<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simulador de Consórcio — R.Torquato</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700;800;900&family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0d0d0d;
--surface: #161616;
--surface2: #1e1e1e;
--border: #2a2a2a;
--accent: #e8a020;
--accent2: #c47d08;
--accent3: #f0b840;
--text: #f0f0f0;
--text-dim: #787878;
--gold: #e8a020;
--red: #e84040;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Montserrat', sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
.bg-grid {
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(232,160,32,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(232,160,32,0.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
}
.bg-glow {
position: fixed;
top: -200px;
left: -200px;
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(232,160,32,0.05) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
.bg-glow2 {
position: fixed;
bottom: -200px;
right: -200px;
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(232,160,32,0.03) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
.container {
position: relative;
z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 40px 24px 80px;
}
header {
margin-bottom: 48px;
animation: fadeUp 0.6s ease;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(232,160,32,0.1);
border: 1px solid rgba(232,160,32,0.3);
color: var(--accent);
font-family: 'Roboto Mono', monospace;
font-size: 10px;
letter-spacing: 2px;
text-transform: uppercase;
padding: 4px 12px;
border-radius: 2px;
margin-bottom: 16px;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
line-height: 1.05;
letter-spacing: -1px;
margin-bottom: 12px;
}
h1 span {
color: var(--accent);
position: relative;
}
.subtitle {
color: var(--text-dim);
font-size: 16px;
font-weight: 400;
max-width: 500px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.grid { grid-template-columns: 1fr; }
.grid-3 { grid-template-columns: 1fr 1fr !important; }
}
@media (max-width: 480px) {
.grid-3 { grid-template-columns: 1fr !important; }
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px;
animation: fadeUp 0.6s ease both;
}
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }
.card-title {
font-size: 11px;
font-family: 'Roboto Mono', monospace;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
}
.card-title::before {
content: '';
display: block;
width: 3px;
height: 12px;
background: var(--accent);
border-radius: 2px;
}
.control-group { margin-bottom: 24px; }
.control-group:last-child { margin-bottom: 0; }
.control-label {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 10px;
}
.control-name {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.control-value {
font-family: 'Roboto Mono', monospace;
font-size: 15px;
font-weight: 700;
color: var(--accent);
}
.slider-wrap {
position: relative;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: var(--border);
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: var(--accent);
border-radius: 50%;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
box-shadow: 0 0 0 0 rgba(0,229,160,0.3);
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 0 6px rgba(0,229,160,0.15);
}
.slider-range {
display: flex;
justify-content: space-between;
margin-top: 6px;
font-family: 'Roboto Mono', monospace;
font-size: 10px;
color: var(--text-dim);
}
/* Tabs */
.tabs {
display: flex;
gap: 4px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
padding: 4px;
margin-bottom: 24px;
}
.tab {
flex: 1;
padding: 8px 4px;
border: none;
background: transparent;
color: var(--text-dim);
font-family: 'Montserrat', sans-serif;
font-size: 12px;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.tab.active {
background: var(--accent);
color: var(--bg);
}
.tab-content { display: none; }
.tab-content.active { display: block; }
/* Result cards */
.results {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px;
margin-bottom: 20px;
animation: fadeUp 0.6s 0.3s ease both;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.metric {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px;
text-align: center;
transition: border-color 0.2s, transform 0.2s;
}
.metric:hover {
border-color: rgba(232,160,32,0.3);
transform: translateY(-2px);
}
.metric-label {
font-size: 10px;
font-family: 'Roboto Mono', monospace;
letter-spacing: 1.5px;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 8px;
}
.metric-value {
font-size: 22px;
font-weight: 800;
line-height: 1;
margin-bottom: 4px;
}
.metric-sub {
font-size: 11px;
color: var(--text-dim);
font-family: 'Roboto Mono', monospace;
}
.metric.green .metric-value { color: var(--accent); }
.metric.blue .metric-value { color: var(--accent2); }
.metric.orange .metric-value { color: var(--accent3); }
.metric.gold .metric-value { color: var(--gold); }
.metric.red .metric-value { color: var(--red); }
/* Chart */
.chart-container {
position: relative;
height: 200px;
margin-top: 8px;
}
canvas {
width: 100% !important;
height: 100% !important;
}
/* Timeline */
.timeline-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px;
animation: fadeUp 0.6s 0.4s ease both;
overflow-x: auto;
}
.timeline-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
min-width: 600px;
}
.timeline-table th {
text-align: left;
padding: 8px 12px;
font-family: 'Roboto Mono', monospace;
font-size: 10px;
letter-spacing: 1.5px;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
}
.timeline-table td {
padding: 10px 12px;
border-bottom: 1px solid rgba(42,42,42,0.8);
font-weight: 600;
}
.timeline-table tr:last-child td { border-bottom: none; }
.timeline-table tr:hover td {
background: rgba(232,160,32,0.04);
}
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 10px;
font-family: 'Roboto Mono', monospace;
font-weight: 700;
}
.tag-green { background: rgba(232,160,32,0.15); color: var(--accent); }
.tag-blue { background: rgba(232,160,32,0.08); color: var(--accent2); }
.tag-orange { background: rgba(232,100,32,0.15); color: #e86420; }
.info-box {
background: rgba(232,160,32,0.05);
border: 1px solid rgba(232,160,32,0.15);
border-radius: 8px;
padding: 14px 16px;
font-size: 12px;
color: var(--text-dim);
line-height: 1.6;
margin-top: 20px;
}
.info-box strong { color: var(--accent); }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.divider {
height: 1px;
background: var(--border);
margin: 20px 0;
}
.highlight-row td { color: var(--accent) !important; }
select {
background: var(--surface2);
border: 1px solid var(--border);
color: var(--text);
font-family: 'Montserrat', sans-serif;
font-size: 14px;
padding: 8px 12px;
border-radius: 6px;
outline: none;
cursor: pointer;
width: 100%;
margin-top: 4px;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<div class="bg-glow"></div>
<div class="bg-glow2"></div>
<div class="container">
<header>
<div class="badge">⬡ R.TORQUATO</div>
<h1>Simulador de<br><span>Consórcio</span></h1>
<p class="subtitle">Calcule sua alavancagem financeira com cartas contempladas — venda ou invista o capital.</p>
</header>
<!-- INPUTS -->
<div class="grid">
<div class="card">
<div class="card-title">Parâmetros do Consórcio</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Crédito da carta</span>
<span class="control-value" id="val-credito">R$ 100.000</span>
</div>
<input type="range" id="credito" min="30000" max="1000000" step="5000" value="100000">
<div class="slider-range"><span>R$ 30k</span><span>R$ 1M</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Parcela mensal</span>
<span class="control-value" id="val-parcela">R$ 1.200</span>
</div>
<input type="range" id="parcela" min="200" max="15000" step="50" value="1200">
<div class="slider-range"><span>R$ 200</span><span>R$ 15k</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Prazo do consórcio</span>
<span class="control-value" id="val-prazo">120 meses (10 anos)</span>
</div>
<input type="range" id="prazo" min="12" max="360" step="12" value="120">
<div class="slider-range"><span>1 ano</span><span>30 anos</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Mês de contemplação estimado</span>
<span class="control-value" id="val-contempl">24º mês</span>
</div>
<input type="range" id="contempl" min="1" max="120" step="1" value="24">
<div class="slider-range"><span>1º mês</span><span>120º mês</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Taxa de adm. total (% do crédito)</span>
<span class="control-value" id="val-taxa">18%</span>
</div>
<input type="range" id="taxa" min="5" max="30" step="0.5" value="18">
<div class="slider-range"><span>5%</span><span>30%</span></div>
</div>
</div>
<div class="card">
<div class="card-title">Estratégia de Uso</div>
<div class="tabs">
<button class="tab active" onclick="setTab('venda')">💰 Venda da Carta</button>
<button class="tab" onclick="setTab('invest')">📈 Investimento</button>
<button class="tab" onclick="setTab('ambos')">⚡ Combo</button>
</div>
<!-- VENDA -->
<div class="tab-content active" id="tab-venda">
<div class="control-group">
<div class="control-label">
<span class="control-name">Ágio na venda da carta (%)</span>
<span class="control-value" id="val-agio">10%</span>
</div>
<input type="range" id="agio" min="0" max="40" step="1" value="10">
<div class="slider-range"><span>0%</span><span>40%</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">IR sobre ganho na venda (%)</span>
<span class="control-value" id="val-ir-venda">15%</span>
</div>
<input type="range" id="ir-venda" min="0" max="27.5" step="0.5" value="15">
<div class="slider-range"><span>0%</span><span>27,5%</span></div>
</div>
</div>
<!-- INVESTIMENTO -->
<div class="tab-content" id="tab-invest">
<div class="control-group">
<div class="control-label">
<span class="control-name">Rentabilidade mensal do investimento</span>
<span class="control-value" id="val-rent">1,0% a.m.</span>
</div>
<input type="range" id="rent" min="0.3" max="3" step="0.05" value="1.0">
<div class="slider-range"><span>0,3%</span><span>3,0%</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Horizon de investimento (anos)</span>
<span class="control-value" id="val-horizon">10 anos</span>
</div>
<input type="range" id="horizon" min="1" max="30" step="1" value="10">
<div class="slider-range"><span>1 ano</span><span>30 anos</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">IR sobre rendimentos (%)</span>
<span class="control-value" id="val-ir-inv">15%</span>
</div>
<input type="range" id="ir-inv" min="0" max="27.5" step="0.5" value="15">
<div class="slider-range"><span>0%</span><span>27,5%</span></div>
</div>
</div>
<!-- COMBO -->
<div class="tab-content" id="tab-ambos">
<div class="control-group">
<div class="control-label">
<span class="control-name">% do crédito vendido como carta</span>
<span class="control-value" id="val-split">50%</span>
</div>
<input type="range" id="split" min="0" max="100" step="10" value="50">
<div class="slider-range"><span>0% (só investe)</span><span>100% (só vende)</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Ágio médio na venda (%)</span>
<span class="control-value" id="val-agio2">10%</span>
</div>
<input type="range" id="agio2" min="0" max="40" step="1" value="10">
<div class="slider-range"><span>0%</span><span>40%</span></div>
</div>
<div class="control-group">
<div class="control-label">
<span class="control-name">Rentabilidade mensal do restante</span>
<span class="control-value" id="val-rent2">1,0% a.m.</span>
</div>
<input type="range" id="rent2" min="0.3" max="3" step="0.05" value="1.0">
<div class="slider-range"><span>0,3%</span><span>3,0%</span></div>
</div>
</div>
<div class="info-box">
<strong>💡 Como funciona a alavancagem:</strong> Você paga parcelas menores que o valor total do crédito. Se contemplado cedo, você obteve acesso a capital muito maior do que pagou até então — e pode usá-lo para gerar rendimento ou vender com ágio.
</div>
</div>
</div>
<!-- RESULTS -->
<div class="results">
<div class="card-title">Resumo da Operação</div>
<div class="grid-3" id="metrics-grid">
<!-- preenchido por JS -->
</div>
<div class="chart-container">
<canvas id="chart"></canvas>
</div>
</div>
<!-- TIMELINE -->
<div class="timeline-section">
<div class="card-title">Projeção por Marco de Tempo</div>
<div style="overflow-x:auto;">
<table class="timeline-table" id="timeline-table">
<!-- preenchido por JS -->
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<script>
let activeTab = 'venda';
let chartInstance = null;
function setTab(tab) {
activeTab = tab;
document.querySelectorAll('.tab').forEach((t,i) => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
const tabs = ['venda','invest','ambos'];
document.querySelectorAll('.tab')[tabs.indexOf(tab)].classList.add('active');
document.getElementById('tab-' + tab).classList.add('active');
update();
}
function fmt(v, decimals=0) {
return 'R$ ' + v.toLocaleString('pt-BR', {minimumFractionDigits: decimals, maximumFractionDigits: decimals});
}
function pct(v, decimals=1) {
return v.toFixed(decimals) + '%';
}
function getInputs() {
return {
credito: +document.getElementById('credito').value,
parcela: +document.getElementById('parcela').value,
prazo: +document.getElementById('prazo').value,
contempl: Math.min(+document.getElementById('contempl').value, +document.getElementById('prazo').value),
taxa: +document.getElementById('taxa').value / 100,
agio: +document.getElementById('agio').value / 100,
irVenda: +document.getElementById('ir-venda').value / 100,
rent: +document.getElementById('rent').value / 100,
horizon: +document.getElementById('horizon').value,
irInv: +document.getElementById('ir-inv').value / 100,
split: +document.getElementById('split').value / 100,
agio2: +document.getElementById('agio2').value / 100,
rent2: +document.getElementById('rent2').value / 100,
};
}
function calc(inp) {
const totalPago = inp.parcela * inp.prazo;
const taxaTotal = inp.credito * inp.taxa;
const pagoAteContemplacao = inp.parcela * inp.contempl;
const custoReal = totalPago; // total desembolsado
const alavancagem = inp.credito / pagoAteContemplacao;
// VENDA
const valorVendaCartaBruto = inp.credito * (1 + inp.agio);
const ganhoVenda = valorVendaCartaBruto - inp.credito;
const irSobreGanhoVenda = ganhoVenda * inp.irVenda;
const valorVendaLiquido = valorVendaCartaBruto - irSobreGanhoVenda;
const lucroVenda = valorVendaLiquido - custoReal;
const roiVenda = lucroVenda / custoReal * 100;
// INVESTIMENTO — reinveste o crédito após contemplação, continua pagando parcelas
const mesesInvestimento = inp.horizon * 12;
const montante = inp.credito * Math.pow(1 + inp.rent, mesesInvestimento);
const rendimentoBruto = montante - inp.credito;
const irRendimento = rendimentoBruto * inp.irInv;
const montanteLiquido = montante - irRendimento;
const custoRealInv = custoReal;
const lucroInv = montanteLiquido - custoRealInv;
const roiInv = lucroInv / custoRealInv * 100;
// COMBO
const parteVendida = inp.credito * inp.split;
const parteInvestida = inp.credito * (1 - inp.split);
const vendaCombo = parteVendida * (1 + inp.agio2);
const ganhoVendaCombo = vendaCombo - parteVendida;
const irVendaCombo = ganhoVendaCombo * 0.15;
const vendaComboLiq = vendaCombo - irVendaCombo;
const mesesInvCombo = inp.horizon * 12;
const montanteCombo = parteInvestida * Math.pow(1 + inp.rent2, mesesInvCombo);
const rendBrutoCombo = montanteCombo - parteInvestida;
const irCombo = rendBrutoCombo * 0.15;
const montanteComboLiq = montanteCombo - irCombo;
const totalCombo = vendaComboLiq + montanteComboLiq;
const lucroCombo = totalCombo - custoReal;
const roiCombo = lucroCombo / custoReal * 100;
return {
totalPago, taxaTotal, pagoAteContemplacao, alavancagem,
valorVendaCartaBruto, valorVendaLiquido, lucroVenda, roiVenda, ganhoVenda, irSobreGanhoVenda,
montante, montanteLiquido, lucroInv, roiInv, rendimentoBruto, irRendimento,
totalCombo, lucroCombo, roiCombo,
custoReal
};
}
function buildMetrics(inp, r) {
const grid = document.getElementById('metrics-grid');
let metrics = [];
const custoFmt = fmt(r.totalPago);
const alavFmt = r.alavancagem.toFixed(1) + 'x';
if (activeTab === 'venda') {
metrics = [
{ label: 'Total Investido', value: fmt(r.totalPago), sub: inp.prazo + ' meses de parcelas', cls: 'blue' },
{ label: 'Crédito Obtido', value: fmt(inp.credito), sub: 'Alavancagem: ' + alavFmt, cls: 'green' },
{ label: 'Venda da Carta (bruto)', value: fmt(r.valorVendaCartaBruto), sub: 'Ágio: ' + pct(inp.agio*100), cls: 'gold' },
{ label: 'IR sobre ganho', value: fmt(r.irSobreGanhoVenda), sub: pct(inp.irVenda*100) + ' sobre ganho', cls: 'red' },
{ label: 'Receita Líquida', value: fmt(r.valorVendaLiquido), sub: 'Após impostos', cls: 'green' },
{ label: 'Lucro / Prejuízo', value: fmt(r.lucroVenda), sub: 'ROI: ' + pct(r.roiVenda), cls: r.lucroVenda >= 0 ? 'green' : 'red' },
];
} else if (activeTab === 'invest') {
metrics = [
{ label: 'Total Investido', value: fmt(r.totalPago), sub: inp.prazo + ' meses de parcelas', cls: 'blue' },
{ label: 'Capital Alavancado', value: fmt(inp.credito), sub: 'Alavancagem: ' + alavFmt, cls: 'green' },
{ label: 'Montante Bruto', value: fmt(r.montante), sub: inp.horizon + ' anos · ' + pct(inp.rent*100) + '/mês', cls: 'gold' },
{ label: 'IR sobre Rendimento', value: fmt(r.irRendimento), sub: pct(inp.irInv*100) + ' sobre rendimento', cls: 'red' },
{ label: 'Montante Líquido', value: fmt(r.montanteLiquido), sub: 'Após impostos', cls: 'green' },
{ label: 'Lucro / Prejuízo', value: fmt(r.lucroInv), sub: 'ROI: ' + pct(r.roiInv), cls: r.lucroInv >= 0 ? 'green' : 'red' },
];
} else {
metrics = [
{ label: 'Total Investido', value: fmt(r.totalPago), sub: inp.prazo + ' meses de parcelas', cls: 'blue' },
{ label: 'Capital Alavancado', value: fmt(inp.credito), sub: 'Alavancagem: ' + alavFmt, cls: 'green' },
{ label: 'Receita Total (Combo)', value: fmt(r.totalCombo), sub: pct(inp.split*100) + ' vendido, ' + pct((1-inp.split)*100) + ' investido', cls: 'gold' },
{ label: 'Custo Real', value: fmt(r.custoReal), sub: 'Parcelas totais', cls: 'blue' },
{ label: 'Lucro / Prejuízo', value: fmt(r.lucroCombo), sub: 'ROI: ' + pct(r.roiCombo), cls: r.lucroCombo >= 0 ? 'green' : 'red' },
{ label: 'Fator de Multiplicação', value: (r.totalCombo / r.custoReal).toFixed(2) + 'x', sub: 'Cada R$1 virou R$' + (r.totalCombo / r.custoReal).toFixed(2), cls: 'orange' },
];
}
grid.innerHTML = metrics.map(m => `
<div class="metric ${m.cls}">
<div class="metric-label">${m.label}</div>
<div class="metric-value">${m.value}</div>
<div class="metric-sub">${m.sub}</div>
</div>
`).join('');
}
function buildChart(inp, r) {
const ctx = document.getElementById('chart').getContext('2d');
if (chartInstance) chartInstance.destroy();
const labels = [];
const paidData = [];
const returnData = [];
const horizonMax = activeTab === 'invest' ? inp.horizon * 12 : inp.prazo;
const steps = Math.min(horizonMax, 360);
const step = Math.max(1, Math.floor(steps / 24));
for (let m = step; m <= steps; m += step) {
labels.push(m >= 12 ? Math.round(m/12) + 'a' : m + 'm');
const paid = inp.parcela * Math.min(m, inp.prazo);
paidData.push(paid);
if (activeTab === 'venda') {
returnData.push(m >= inp.contempl ? r.valorVendaLiquido : 0);
} else if (activeTab === 'invest') {
const invMonths = Math.max(0, m - inp.contempl);
const mont = inp.credito * Math.pow(1 + inp.rent, invMonths);
const rend = mont - inp.credito;
returnData.push(mont - rend * inp.irInv);
} else {
const invMonths = Math.max(0, m - inp.contempl);
const parteInv = inp.credito * (1 - inp.split);
const mont = parteInv * Math.pow(1 + inp.rent2, invMonths);
const parteVend = m >= inp.contempl ? inp.credito * inp.split * (1 + inp.agio2) : 0;
returnData.push(mont + parteVend);
}
}
chartInstance = new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [
{
label: 'Total Pago',
data: paidData,
borderColor: '#787878',
backgroundColor: 'rgba(120,120,120,0.06)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 0,
},
{
label: activeTab === 'venda' ? 'Receita Venda' : activeTab === 'invest' ? 'Montante Líquido' : 'Retorno Combo',
data: returnData,
borderColor: '#e8a020',
backgroundColor: 'rgba(232,160,32,0.08)',
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 0,
},
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: {
labels: { color: '#787878', font: { family: 'Roboto Mono', size: 11 }, boxWidth: 12 }
},
tooltip: {
backgroundColor: '#161616',
borderColor: '#2a2a2a',
borderWidth: 1,
titleColor: '#e8edf5',
bodyColor: '#6b7a8d',
callbacks: {
label: ctx => ' ' + ctx.dataset.label + ': R$ ' + ctx.raw.toLocaleString('pt-BR', {maximumFractionDigits: 0})
}
}
},
scales: {
x: { grid: { color: 'rgba(30,42,58,0.5)' }, ticks: { color: '#6b7a8d', font: { family: 'Space Mono', size: 10 }, maxTicksLimit: 12 } },
y: { grid: { color: 'rgba(30,42,58,0.5)' }, ticks: { color: '#6b7a8d', font: { family: 'Space Mono', size: 10 }, callback: v => 'R$' + (v >= 1000000 ? (v/1000000).toFixed(1)+'M' : v >= 1000 ? (v/1000).toFixed(0)+'k' : v) } }
}
}
});
}
function buildTimeline(inp, r) {
const marcos = [1, 2, 3, 5, 7, 10, 15, 20, 30].filter(y => y * 12 <= inp.prazo + inp.horizon * 12 + 12);
let rows = '';
for (const anos of marcos) {
const meses = anos * 12;
const paid = fmt(inp.parcela * Math.min(meses, inp.prazo));
const contempl = meses >= inp.contempl ? '<span class="tag tag-green">Sim</span>' : '<span class="tag tag-orange">Não</span>';
let retorno = '—';
let lucro = '—';
let roi = '—';
if (meses >= inp.contempl) {
if (activeTab === 'venda') {
retorno = fmt(r.valorVendaLiquido);
const lv = r.valorVendaLiquido - inp.parcela * Math.min(meses, inp.prazo);
lucro = fmt(lv);
roi = pct(lv / (inp.parcela * Math.min(meses, inp.prazo)) * 100);
} else if (activeTab === 'invest') {
const invM = meses - inp.contempl;
const mont = inp.credito * Math.pow(1 + inp.rent, invM);
const rend = mont - inp.credito;
const liq = mont - rend * inp.irInv;
const pago = inp.parcela * Math.min(meses, inp.prazo);
retorno = fmt(liq);
lucro = fmt(liq - pago);
roi = pct((liq - pago) / pago * 100);
} else {
const invM = meses - inp.contempl;
const parteInv = inp.credito * (1 - inp.split);
const mont = parteInv * Math.pow(1 + inp.rent2, invM);
const parteVend = inp.credito * inp.split * (1 + inp.agio2);
const total = mont + parteVend;
const pago = inp.parcela * Math.min(meses, inp.prazo);
retorno = fmt(total);
lucro = fmt(total - pago);
roi = pct((total - pago) / pago * 100);
}
}
const isHighlight = meses === inp.prazo || (inp.prazo > 360 && anos === 20);
rows += `<tr ${isHighlight ? 'class="highlight-row"' : ''}>
<td><strong>${anos} ${anos === 1 ? 'ano' : 'anos'}</strong></td>
<td>${paid}</td>
<td>${contempl}</td>
<td>${retorno}</td>
<td>${lucro}</td>
<td>${roi}</td>
</tr>`;
}
document.getElementById('timeline-table').innerHTML = `
<thead>
<tr>
<th>Marco</th>
<th>Total Pago</th>
<th>Contemplado?</th>
<th>Retorno Potencial</th>
<th>Lucro Estimado</th>
<th>ROI</th>
</tr>
</thead>
<tbody>${rows}</tbody>
`;
}
function bindSlider(id, displayId, formatter) {
const el = document.getElementById(id);
el.addEventListener('input', () => {
document.getElementById(displayId).textContent = formatter(+el.value);
update();
});
}
function update() {
const inp = getInputs();
// clamp contempl to prazo
const contemplEl = document.getElementById('contempl');
contemplEl.max = inp.prazo;
if (inp.contempl > inp.prazo) {
contemplEl.value = inp.prazo;
document.getElementById('val-contempl').textContent = inp.prazo + 'º mês';
}
const r = calc(inp);
buildMetrics(inp, r);
buildChart(inp, r);
buildTimeline(inp, r);
}
// Bind sliders
bindSlider('credito', 'val-credito', v => 'R$ ' + v.toLocaleString('pt-BR'));
bindSlider('parcela', 'val-parcela', v => 'R$ ' + v.toLocaleString('pt-BR'));
bindSlider('prazo', 'val-prazo', v => v + ' meses (' + Math.round(v/12 * 10)/10 + ' anos)');
bindSlider('contempl', 'val-contempl', v => v + 'º mês');
bindSlider('taxa', 'val-taxa', v => v + '%');
bindSlider('agio', 'val-agio', v => v + '%');
bindSlider('ir-venda', 'val-ir-venda', v => v + '%');
bindSlider('rent', 'val-rent', v => v.toFixed(2) + '% a.m.');
bindSlider('horizon', 'val-horizon', v => v + ' anos');
bindSlider('ir-inv', 'val-ir-inv', v => v + '%');
bindSlider('split', 'val-split', v => v + '%');
bindSlider('agio2', 'val-agio2', v => v + '%');
bindSlider('rent2', 'val-rent2', v => v.toFixed(2) + '% a.m.');
// Initial render
update();
</script>
</body>
</html>