// Recreated product mockups in pure HTML/CSS — these stand in for the real product screenshots // Scale-to-fit wrapper: locks children to a design width and CSS-scales them // proportionally to the container. Avoids reflow + preserves visual fidelity. const MockupFit = ({ designWidth = 880, mobileMaxWidth = 720, children }) => { const wrapRef = React.useRef(null); const innerRef = React.useRef(null); const [scale, setScale] = React.useState(1); const [innerH, setInnerH] = React.useState(null); React.useEffect(() => { const update = () => { const w = wrapRef.current?.clientWidth ?? designWidth; // only scale below threshold; above, render at natural width if (w >= mobileMaxWidth) { setScale(1); setInnerH(null); return; } const s = w / designWidth; setScale(s); const ih = innerRef.current?.scrollHeight ?? 0; setInnerH(ih * s); }; update(); const ro = new ResizeObserver(update); if (wrapRef.current) ro.observe(wrapRef.current); if (innerRef.current) ro.observe(innerRef.current); window.addEventListener('resize', update); return () => { ro.disconnect(); window.removeEventListener('resize', update); }; }, [designWidth, mobileMaxWidth]); return (
{children}
); }; const mockupStyles = { app: { background: 'white', borderRadius: 14, boxShadow: '0 1px 0 rgba(0,0,0,0.04), 0 30px 60px -20px rgba(20,17,15,0.18), 0 12px 24px -12px rgba(20,17,15,0.08)', border: '1px solid rgba(20,17,15,0.06)', overflow: 'hidden', fontFamily: 'var(--sans)', color: 'var(--ink)', fontSize: 12, width: '100%', }, bar: { height: 36, background: '#FBF9F5', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 8, padding: '0 14px', fontSize: 11, color: 'var(--ink-3)', }, dot: (c) => ({ width: 10, height: 10, borderRadius: '50%', background: c }), }; // Citrus wheel mark — matches the brand const FrutionMark = ({ size = 22 }) => ( {[0, 1, 2, 3, 4, 5, 6, 7].map(i => { const angle = (i * 45 - 90) * Math.PI / 180; const next = ((i + 1) * 45 - 90) * Math.PI / 180; const x1 = 50 + Math.cos(angle) * 50; const y1 = 50 + Math.sin(angle) * 50; const x2 = 50 + Math.cos(next) * 50; const y2 = 50 + Math.sin(next) * 50; const colors = ['#F08A2C','#FFB066','#F08A2C','#FFB066','#F08A2C','#FFB066','#F08A2C','#FFB066']; return ; })} ); // ============ CRM dashboard ============ const CRMMockup = () => { const dealsByStage = [ { stage: 'Qualificação', count: 24, value: 'R$ 312k', color: '#94A3B8' }, { stage: 'Proposta', count: 18, value: 'R$ 580k', color: '#F08A2C' }, { stage: 'Negociação', count: 12, value: 'R$ 410k', color: '#D9711A' }, { stage: 'Fechamento', count: 7, value: 'R$ 290k', color: '#16A34A' }, ]; const deals = [ { co: 'Bruke Indústria', val: 'R$ 84.500', stage: 'Negociação', owner: 'AC', days: '3d' }, { co: 'Diprotec Ltda', val: 'R$ 42.800', stage: 'Proposta', owner: 'MR', days: '1d' }, { co: 'Flexoseal Comercial', val: 'R$ 128.000', stage: 'Negociação', owner: 'AC', days: '7d' }, { co: 'Qualita Serviços', val: 'R$ 19.300', stage: 'Qualificação', owner: 'JS', days: '12d' }, { co: 'Wtec Soluções', val: 'R$ 67.900', stage: 'Proposta', owner: 'MR', days: '2d' }, ]; return (
{/* top chrome */}
frution.app/crm/pipeline
{/* layout */}
{/* sidebar */}
frution
Módulos
{[ ['CRM', true], ['BPM', false], ['ERP', false], ['OmniChannel', false], ['Tracking', false] ].map(([n,a]) => (
{n}
))}
CRM
{['Pipeline','Contatos','Empresas','Atividades','Relatórios'].map((n,i) => (
{n}
))}
{/* main */}
CRM · Pipeline
Negócios em andamento
Filtrar
+ Novo negócio
{/* stats */}
{dealsByStage.map(s => (
{s.stage}
{s.value}
{s.count} negócios
))}
{/* chart */}
Receita prevista · últimos 12 meses
R$ 1,59M total
{/* deals list */}
Empresa
Valor
Etapa
Resp.
Idade
{deals.map((d,i) => (
{d.co}
{d.val}
{d.stage}
{d.owner}
{d.days}
))}
); }; // ============ BPM workflow ============ const BPMMockup = () => { return (
frution.app/bpm/aprovacao-compras
BPM · Fluxo
Aprovação de compras · v3.2
Ativo · 47 instâncias
{/* canvas */}
{/* connections */} PR criada ≤ 5k {'> 5k'} aprovado {/* nodes */} {[ {l:35,t:60, label:'Início', sub:'Solicitante', color:'#16A34A', shape:'circle'}, {l:180,t:55, label:'Validar dados', sub:'Auto · 30s', color:'#5C544E'}, {l:360,t:55, label:'Aprovar até R$ 5k?', sub:'Decisão', color:'#F08A2C', shape:'diamond'}, {l:180,t:185, label:'Aprovação Diretor', sub:'Manuel R. · 2/3', color:'#F08A2C', highlight: true}, {l:420,t:185, label:'PO no ERP', sub:'Auto · integrado', color:'#5C544E'}, {l:580,t:60, label:'Concluído', sub:'47 hoje', color:'#16A34A', shape:'circle'}, ].map((n,i) => { if (n.shape === 'circle') { return (
{n.label}
{n.sub}
); } return (
{n.label}
{n.shape !== 'diamond' &&
{n.sub}
}
); })} {/* current instance pill */}
#PR-2026-0418 aguardando aprovação · 1h32
); }; // ============ ERP — financial ============ const ERPMockup = () => { const months = ['J','F','M','A','M','J','J','A','S','O','N','D']; const revenue = [42,48,51,55,62,58,68,72,75,82,88,94]; const cost = [28,32,33,36,38,40,42,45,48,50,53,56]; const max = 100; return (
frution.app/erp/financeiro
ERP · Financeiro
Resumo executivo · 2026
{['Mensal','Trimestre','Ano'].map((p,i) => (
{p}
))}
{/* big KPIs */}
{[ ['Receita YTD', 'R$ 8.74M', '+18.2%', true], ['Custos', 'R$ 4.92M', '+11.4%', false], ['Margem', '43.7%', '+2.1pp', true], ['Estoque', 'R$ 1.23M', '−4.8%', true], ].map(([l,v,d,pos],i) => (
{l}
{v}
{d}
))}
{/* chart */}
Receita vs. custos · evolução
Receita
Custos
{[0,1,2,3].map(i => )} {/* revenue bars */} {revenue.map((v,i) => ( {months[i]} ))}
{/* table */}
Conta
Saldo
Vencendo
Status
{[ ['Contas a receber', 'R$ 1.847.300', 'R$ 234k em 7d', 'no prazo'], ['Contas a pagar', 'R$ 982.450', 'R$ 118k em 3d', 'atenção'], ['Boletos emitidos · abr', 'R$ 612.800', '184 títulos', 'no prazo'], ].map((r,i) => (
{r[0]}
{r[1]}
{r[2]}
{r[3]}
))}
); }; // ============ OmniChannel ============ const OmniMockup = () => { const channels = [ {n:'WhatsApp', c:'#25D366', count: 47}, {n:'Email', c:'#5C544E', count: 23}, {n:'Instagram', c:'#E1306C', count: 12}, {n:'Web Chat', c:'#F08A2C', count: 8}, {n:'Facebook', c:'#1877F2', count: 4}, ]; const conversations = [ {n:'Carla Mendes', co:'Bruke', ch:'WhatsApp', m:'Pode me enviar a proposta atualizada?', t:'agora', un:2, c:'#25D366'}, {n:'Roberto Silva', co:'Diprotec', ch:'Email', m:'Confirmando pedido #4821 para entrega...', t:'5min', un:0, c:'#5C544E'}, {n:'@flexoseal', co:'', ch:'Instagram', m:'Vocês fazem entrega para SC?', t:'12min', un:1, c:'#E1306C'}, {n:'João Costa', co:'Wtec', ch:'WhatsApp', m:'Obrigado pelo retorno rápido!', t:'34min', un:0, c:'#25D366'}, {n:'Lead site', co:'', ch:'Web Chat', m:'Olá, gostaria de saber sobre o módulo...', t:'1h', un:0, c:'#F08A2C'}, ]; return (
frution.app/omni/inbox
{/* channel rail */}
Canais
Todos94
{channels.map(c => (
{c.n} {c.count}
))}
Etiquetas
{[['Urgente','#DC2626'],['Vendas','#F08A2C'],['Suporte','#16A34A'],['Pós-venda','#0891B2']].map(([n,c]) => (
{n}
))}
{/* conv list */}
Inbox
🔍 buscar conversas...
{conversations.map((c,i) => (
{c.n} {c.co && · {c.co}}
{c.un > 0 && {c.un}} {c.t}
{c.m}
))}
{/* conversation */}
Carla Mendes
WhatsApp · Bruke Indústria · Online
VENDAS
HOJE · 14:32
Oi! Tudo bem? Recebi seu email mas queria conferir um detalhe do pedido.
14:32
Olá Carla! Claro, pode me dizer o número do pedido?
14:33 ✓✓
Pode me enviar a proposta atualizada?
agora
Digitar mensagem...
Enviar
); }; // ============ Tracking ============ const TrackingMockup = () => { const sources = [ {key:'gads', label:'Google Ads', color:'#4285F4', count:412, pct:34, conv:48, cpl:'R$ 38'}, {key:'meta', label:'Meta Ads', color:'#1877F2', count:267, pct:22, conv:31, cpl:'R$ 52'}, {key:'org', label:'Orgânico', color:'#16A34A', count:218, pct:18, conv:42, cpl:'R$ 0'}, {key:'gpt', label:'ChatGPT', color:'#10A37F', count:148, pct:12, conv:22, cpl:'R$ 0'}, {key:'ref', label:'Indicação', color:'#A855F7', count:108, pct:9, conv:35, cpl:'R$ 0'}, {key:'dir', label:'Direto', color:'#5C544E', count:62, pct:5, conv:9, cpl:'R$ 0'}, ]; const conversations = [ {name:'Mariana Soares', co:'Bruke', msg:'Vi o anúncio sobre automação de processos...', src:'gads', srcLabel:'Google Ads', srcColor:'#4285F4', glyph:'G', when:'agora', kw:'automação industrial'}, {name:'Rafael Lima', co:'Diprotec', msg:'Cheguei pelo Instagram, queria saber valores', src:'meta', srcLabel:'Meta Ads', srcColor:'#1877F2', glyph:'f', when:'14 min', kw:'campanha · CRM PME'}, {name:'Júlia Carvalho', co:'Flexoseal', msg:'Pesquisei "CRM pra indústria" no Google', src:'org', srcLabel:'Orgânico', srcColor:'#16A34A', glyph:'↗', when:'1h', kw:'crm para indústria'}, {name:'Pedro Henrique', co:'—', msg:'O ChatGPT me indicou vocês como ERP nacional', src:'gpt', srcLabel:'ChatGPT', srcColor:'#10A37F', glyph:'✦', when:'2h', kw:'erp brasileiro mid-market'}, {name:'Carla Mendes', co:'Wtec', msg:'Recebi indicação da Bruke sobre o Frution', src:'ref', srcLabel:'Indicação', srcColor:'#A855F7', glyph:'↺', when:'3h', kw:'cliente Bruke'}, ]; return (
frution.app/tracking/conversas
Tracking · origem das conversas
Conversas WhatsApp por canal
{['Hoje','7d','30d','90d'].map((p,i) => (
{p}
))}
{/* attribution panel */}
1.215
↑ 18,4%
Conversas atribuídas · últimos 30 dias
{/* stacked horizontal bar */}
{sources.map(s => (
))}
0%100%
{sources.map((s,i) => (
{s.label[0]}
{s.label}
{s.conv} viraram cliente · CPL {s.cpl}
{s.count}
{s.pct}%
))}
{/* live conversations list */}
Últimas conversas
ao vivo
{conversations.map((c,i) => (
{c.name.split(' ').map(w => w[0]).slice(0,2).join('')}
{c.name} {c.co !== '—' && · {c.co}}
{c.msg}
{c.glyph} {c.srcLabel} {c.kw}
{c.when}
))}
Google Ads converte 11,7% — melhor ROI do mês
); }; Object.assign(window, { CRMMockup, BPMMockup, ERPMockup, OmniMockup, TrackingMockup, FrutionMark, MockupFit });