// mobile.jsx — Comissa no celular + frame com identidade do app
const { useState: _ms, useEffect: _me, useRef: _ur } = React;
// ─────────────────────────────────────────────────────────────
// Ícones da tab bar (estilo SF Symbols line, 22px)
// ─────────────────────────────────────────────────────────────
const TabIcon = ({ kind, active = false, size = 22 }) => {
const sw = active ? 2 : 1.7;
const props = {
width: size, height: size, viewBox: '0 0 24 24',
fill: 'none', stroke: 'currentColor',
strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round',
};
if (kind === 'home') return (
);
if (kind === 'vendas') return (
);
if (kind === 'metas') return (
);
if (kind === 'perfil') return (
);
return null;
};
// ─────────────────────────────────────────────────────────────
// MobileApp — Comissa no iPhone (tabs Início / Vendas / Metas / Perfil)
// ─────────────────────────────────────────────────────────────
const M_FONT = "'Geist', -apple-system, system-ui, sans-serif";
const M_MONO = "'Geist Mono', ui-monospace, monospace";
const M_VENDAS = [
{ t: 'Rolex Submariner', c: 'Pedro Marques', v: 78000, com: 6260, status: '#059669', day: 'Hoje · 14:32' },
{ t: 'Curso Trading Pro', c: 'Ana Souza', v: 1997, com: 599, status: '#F59E0B', day: 'Hoje · 11:08' },
{ t: 'Honda Civic 2024', c: 'Vista Verde', v: 142000, com: 7100, status: '#94A3B4', day: 'Ontem' },
{ t: 'iPhone 16 Pro', c: 'Mariana R.', v: 9499, com: 475, status: '#059669', day: 'Ontem' },
{ t: 'Apto 2Q Pinheiros', c: 'Família Lima', v: 580000, com: 17400, status: '#F59E0B', day: '2 dias' },
{ t: 'Curso Inglês', c: '24 alunos', v: 47928, com: 9586, status: '#059669', day: '3 dias' },
];
function MobileHomeView() {
// valores ao vivo (driftam devagar, tween suave na exibição)
const comissao = window.useDrift({
start: 12840, min: 12700, max: 13620,
stepMin: 8, stepMax: 38, upBias: 0.74, intervalMs: 2200,
});
const vendas = window.useDrift({
start: 12, min: 12, max: 18,
stepMin: 0.15, stepMax: 0.55, upBias: 0.62, intervalMs: 2800,
});
const ticket = window.useDrift({
start: 6500, min: 6280, max: 6920,
stepMin: 6, stepMax: 22, upBias: 0.55, intervalMs: 2600,
});
const conv = window.useDrift({
start: 34, min: 32, max: 36,
stepMin: 0.05, stepMax: 0.22, upBias: 0.5, intervalMs: 3000,
});
const META = 16000;
const pct = Math.min(100, (comissao / META) * 100);
const falta = Math.max(0, META - comissao);
const TN = window.TweenedNumber;
const brl = (n) => 'R$ ' + Math.round(n).toLocaleString('pt-BR');
const brlk = (n) => 'R$ ' + (n / 1000).toFixed(1).replace('.', ',') + 'k';
return (
{/* header */}
Olá, Marina
Hoje é dia de fechar
MS
{/* card comissão */}
COMISSÃO · MAIO
↑ +24%
vs abril
Meta de R$ 16.000
Math.round(n) + '%'} />
Faltam . Você consegue.
{/* mini KPIs */}
{[
{ l: 'Vendas', v: vendas, fmt: (n) => Math.round(n).toString(), d: '+3 hoje' },
{ l: 'Ticket', v: ticket, fmt: brlk, d: '+R$ 380' },
{ l: 'Conv.', v: conv, fmt: (n) => Math.round(n) + '%', d: '−2pp' },
].map((k) => (
))}
{/* vendas recentes */}
{M_VENDAS.slice(0, 4).map((v, i) => (
R$ {v.v.toLocaleString('pt-BR')}
+R$ {v.com.toLocaleString('pt-BR')}
))}
);
}
function MobileVendasView() {
const [filter, setFilter] = _ms('todas');
const filtros = [
{ k: 'todas', l: 'Todas' },
{ k: 'abertas', l: 'Abertas' },
{ k: 'pagas', l: 'Pagas' },
];
const filtered = filter === 'pagas'
? M_VENDAS.filter((v) => v.status === '#059669')
: filter === 'abertas'
? M_VENDAS.filter((v) => v.status !== '#059669')
: M_VENDAS;
return (
Suas vendas
12 abertas · 8 pagas
{/* busca */}
{/* filtros */}
{filtros.map((f) => (
setFilter(f.k)} style={{
padding: '6px 12px', borderRadius: 999,
background: filter === f.k ? '#FBFCFD' : '#fff',
color: filter === f.k ? '#fff' : 'rgba(13,17,23,0.65)',
border: '0.5px solid rgba(13,17,23,0.08)', fontFamily: 'inherit',
fontSize: 12, fontWeight: 500, cursor: 'pointer',
}}>{f.l}
))}
{/* lista */}
{filtered.map((v, i) => (
R$ {v.v.toLocaleString('pt-BR')}
+R$ {v.com.toLocaleString('pt-BR')}
))}
);
}
function MobileMetasView() {
return (
Metas
Maio · faltam 11 dias
{[
{ l: 'Comissão pessoal', cur: 12840, max: 16000, color: '#2563EB', sub: 'Faltam R$ 3.160' },
{ l: 'Vendas no mês', cur: 12, max: 18, color: '#38BDF8', sub: '+3 hoje', fmt: 'count' },
{ l: 'Ticket médio', cur: 6500, max: 7500, color: 'var(--green-text)', sub: '+R$ 380 vs abr' },
].map((m) => {
const pct = Math.min(100, Math.round((m.cur / m.max) * 100));
return (
{m.fmt === 'count' ? m.cur : `R$ ${m.cur.toLocaleString('pt-BR')}`}
/ {m.fmt === 'count' ? m.max : `R$ ${m.max.toLocaleString('pt-BR')}`}
{m.sub}
);
})}
Ranking do time · maio
{[
{ p: 1, n: 'Beatriz F.', v: 18420, you: false, hue: 30 },
{ p: 2, n: 'Você', v: 12840, you: true, hue: 14 },
{ p: 3, n: 'Carlos O.', v: 11280, you: false, hue: 30 },
{ p: 4, n: 'Felipe C.', v: 9460, you: false, hue: 42 },
].map((r, i) => (
{r.p}
{r.n.split(' ').map((s) => s[0]).join('').slice(0, 2)}
{r.n}
R$ {r.v.toLocaleString('pt-BR')}
))}
);
}
function MobilePerfilView() {
return (
MS
Marina Souza
Vendedora · time Velocittà
{[
{ l: 'Vendas', v: '147' },
{ l: 'Clientes', v: '62' },
{ l: 'Streak', v: '9 d' },
].map((s) => (
))}
{[
{ i: IconGrid, l: 'Configurações da conta', s: 'Nome, email, senha, Face ID' },
{ i: IconBolt, l: 'Notificações', s: 'Nova venda, meta, follow-up' },
{ i: IconPix, l: 'Pagamento e Pix', s: 'Como sua comissão cai' },
{ i: IconUsers, l: 'Time Velocittà', s: '6 vendedores · você é o #2' },
{ i: IconCircle, l: 'Ajuda & contato', s: 'WhatsApp do suporte' },
{ i: IconArrow, l: 'Sair', s: 'Encerrar sessão neste celular', danger: true },
].map((row, i) => (
))}
Comissa · versão 2.4.1
);
}
function NovaVendaSheet({ onClose }) {
return (
e.stopPropagation()} style={{
width: '100%', background: '#F0F4F8',
borderRadius: '24px 24px 0 0',
padding: '14px 18px 34px',
animation: 'cd-modal-pop .35s cubic-bezier(.2,.8,.2,1)',
}}>
Nova ação
O que você quer registrar?
{[
{ i: IconMoney, l: 'Nova venda', s: 'Adicionar venda com comissão' },
{ i: IconUsers, l: 'Novo cliente', s: 'Cadastrar lead ou cliente' },
{ i: IconClock, l: 'Novo lembrete', s: 'Follow-up, ligação, reunião' },
{ i: IconTarget, l: 'Atualizar meta', s: 'Mudar meta de maio' },
].map((x) => (
›
))}
);
}
const PHONE_NOTIF_PRODUCTS = [
{ p: 'Rolex Submariner', c: 'Pedro M.' },
{ p: 'Honda Civic 2024', c: 'Vista Verde' },
{ p: 'Apto 2Q Pinheiros', c: 'Família Lima' },
{ p: 'Curso Trading Pro', c: 'Ana S.' },
{ p: 'iPhone 16 Pro Max', c: 'Mariana R.' },
{ p: 'MacBook Air M3', c: 'Lucas O.' },
{ p: 'Yamaha MT-07', c: 'Caio B.' },
{ p: 'Toyota Corolla XEi', c: 'Renata F.' },
];
function PhoneNotifCard({ note, onDismiss }) {
const [drag, setDrag] = _ms({ dragging: false, dy: 0 });
const startY = _ur(0);
const onPointerDown = (e) => {
startY.current = e.clientY;
setDrag({ dragging: true, dy: 0 });
try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
};
const onPointerMove = (e) => {
if (!drag.dragging) return;
const d = e.clientY - startY.current;
setDrag({ dragging: true, dy: Math.min(0, d) });
};
const endDrag = () => {
if (drag.dy < -28) {
onDismiss();
} else {
setDrag({ dragging: false, dy: 0 });
}
};
return (
e.stopPropagation()}
onClick={onDismiss}
style={{
position: 'absolute', top: 6, right: 6,
width: 20, height: 20, borderRadius: 999,
background: 'rgba(13,17,23,0.06)',
border: 0, padding: 0, cursor: 'pointer',
color: 'rgba(13,17,23,0.55)',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
fontFamily: 'inherit', fontSize: 12, lineHeight: 1,
}}
>
C
Vendido! +R$ {note.valor.toLocaleString('pt-BR')}
{note.produto} · {note.cliente}
);
}
function PhoneNotifications({ startDelayMs = 1500, intervalMs = 35000, lifetimeMs = 20000, fadeMs = 600 }) {
const [notes, setNotes] = _ms([]);
_me(() => {
let mounted = true;
let id = 0;
let interval = null;
const timers = new Set();
const addTimer = (fn, ms) => {
const t = setTimeout(() => { timers.delete(t); fn(); }, ms);
timers.add(t);
};
const spawn = () => {
if (!mounted) return;
id += 1;
const myId = id;
const it = PHONE_NOTIF_PRODUCTS[Math.floor(Math.random() * PHONE_NOTIF_PRODUCTS.length)];
const valor = Math.floor(2 + Math.random() * 78) * 1000 + Math.floor(Math.random() * 999);
setNotes(n => [{ id: myId, valor, produto: it.p, cliente: it.c, fading: false }, ...n].slice(0, 2));
// inicia o fade um pouco antes do fim
addTimer(() => {
if (!mounted) return;
setNotes(n => n.map(x => x.id === myId ? { ...x, fading: true } : x));
}, Math.max(0, lifetimeMs - fadeMs));
// remove de fato após o tempo de vida
addTimer(() => {
if (!mounted) return;
setNotes(n => n.filter(x => x.id !== myId));
}, lifetimeMs);
};
const startTimer = setTimeout(() => {
if (!mounted) return;
spawn();
interval = setInterval(spawn, intervalMs);
}, startDelayMs);
return () => {
mounted = false;
clearTimeout(startTimer);
if (interval) clearInterval(interval);
timers.forEach(clearTimeout);
};
}, [startDelayMs, intervalMs, lifetimeMs, fadeMs]);
const dismiss = (id) => setNotes(n => n.filter(x => x.id !== id));
return (
{notes.map(n => (
dismiss(n.id)} />
))}
);
}
function ComissaMobileApp() {
const [tab, setTab] = _ms('home');
const [sheet, setSheet] = _ms(null);
const view =
tab === 'home' ? :
tab === 'vendas' ? :
tab === 'metas' ? :
tab === 'perfil' ? :
null;
return (
{view}
{[
{ k: 'home', l: 'Início' },
{ k: 'vendas', l: 'Vendas' },
{ k: 'fab', fab: true },
{ k: 'metas', l: 'Metas' },
{ k: 'perfil', l: 'Perfil' },
].map((b) => (
b.fab ? (
setSheet('nova')} aria-label="Nova ação" style={{
flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center',
border: 0, background: 'transparent', padding: 0, cursor: 'pointer',
}}>
) : (
setTab(b.k)} style={{
flex: 1, display: 'flex', flexDirection: 'column',
alignItems: 'center', gap: 2,
padding: '2px 0', border: 0, background: 'transparent', cursor: 'pointer',
color: tab === b.k ? '#2563EB' : 'rgba(13,17,23,0.45)',
fontFamily: 'inherit',
transition: 'color .2s ease',
}}>
{b.l}
)
))}
{sheet &&
setSheet(null)} />}
);
}
// ─────────────────────────────────────────────────────────────
// ComissaNotif — versão alternativa do iPhone: lock screen
// com notificação de venda
// ─────────────────────────────────────────────────────────────
function ComissaLockScreen() {
return (
{/* glow */}
{/* hora */}
{/* notificação */}
Vendido! +R$ 6.260
Pedro Marques fechou o Rolex Submariner. Comissão cai via Pix em 1 dia útil.
{/* notificação 2 */}
Falta R$ 3.160 pra meta de maio
Você bate fácil até sexta.
{/* bottom hint */}
Toque pra abrir o Comissa
);
}
// ─────────────────────────────────────────────────────────────
// AppIdentity — frame "este é o app": ícone, nome, splash
// ─────────────────────────────────────────────────────────────
function AppIdentityFrame({ width = 720, height = 880 }) {
return (
{/* topo */}
identidade · app
{/* centro ícone gigante */}
Comissa
Painel pra quem vende.
Carro, curso, relógio, imóvel. Tudo num lugar só.
{/* badges de loja */}
{[
{ l1: 'Disponível na', l2: 'App Store' },
{ l1: 'Disponível no', l2: 'Google Play' },
].map((b) => (
{b.l2 === 'App Store' ? '' : '▶'}
{b.l1.toUpperCase()}
{b.l2}
))}
{/* paleta de ícones em vários tamanhos */}
Variações de ícone
{[120, 84, 60, 44, 32].map((s) => (
))}
);
}
// O ícone do app — gradiente roxo→ciano com a marca de onda
function AppIcon({ size = 120, dark = false, mono = false }) {
const radius = size * 0.22;
const bg = mono
? '#FBFCFD'
: dark
? 'linear-gradient(135deg, #F4F8FB 0%, #E8F1FE 100%)'
: 'linear-gradient(135deg, #2563EB 0%, #38BDF8 100%)';
return (
);
}
Object.assign(window, {
ComissaMobileApp, ComissaLockScreen, AppIdentityFrame, AppIcon,
});