← Biblioteca componentes/conversao/calculadora-sucessao/ficha.md CódigoTela cheia
conversao/calculadora-sucessao

Calculadora de sucessão

🟢 estavel complexidade alta prefixo suc- calculadora sucessao inventario holding itcmd comparativo ferramenta range select compliance acessibilidade sem-lib

Incluir · as linhas que se colam num site novo, depois do core (CLAUDE.md §10)

<link rel="stylesheet" href="componentes/conversao/calculadora-sucessao/estilo.css">
<script src="componentes/conversao/calculadora-sucessao/script.js"></script>

markup.html · 218 linhas · 10,8 KB

<!-- ==========================================================================
     Sucessão — calculadora de custo e prazo
     Prefixo: suc-
     Requer: core (tokens, base, motion.css, motion.js) + estilo.css e script.js

     Três controles (patrimônio, estado, herdeiros) e dois cartões que se
     recalculam: inventário judicial contra holding familiar com doação em
     vida. Cada cartão soma parcelas VISÍVEIS — soma que não mostra as
     parcelas é chute com aparência de conta.

     COMPLIANCE — leia antes de publicar:
     · Os percentuais são ILUSTRATIVOS, referência 2026, e vivem numa tabela
       no topo do script.js. ITCMD, custas e honorários mudam por estado, por
       ano e por caso: troque pela tabela conferida com o jurídico do cliente.
     · O ITCMD aparece nos DOIS cartões, com o MESMO valor. É intencional:
       holding não elimina imposto — antecipa e organiza. Tirar essa linha do
       cartão "com estrutura" transformaria a peça numa promessa de isenção.
     · Prazo é sempre FAIXA ("de 16 a 36 meses"), nunca número único.
     · O texto nunca diz "você vai economizar". Diz "nesta simulação, a
       diferença entre os dois caminhos é X".
     · Nada aqui coleta nome, e-mail ou telefone, e nada vai para
       localStorage. A captura é do conversao/cta-formulario-lead, para onde
       o botão final aponta.

     Sem JavaScript: os três controles continuam navegáveis e os cartões
     mostram o caso inicial escrito no HTML (R$ 8.000.000 · São Paulo ·
     3 herdeiros). O <noscript> avisa que os números não recalculam.
     ========================================================================== -->

<section class="sec-paper sec-pad" id="sucessao">
  <div class="wrap">

    <div class="sec-head suc-head">
      <span class="eyebrow" data-rv>Sucessão</span>
      <h2 class="sec-title ttl">O que custa <em>não</em> planejar</h2>
      <p class="sec-lead" data-rv data-d="1">Transferir patrimônio tem custo e prazo nos dois caminhos.
        O que muda é quando a conta é feita — em vida, com estrutura, ou depois, no inventário.</p>
    </div>

    <!-- CONTEÚDO ILUSTRATIVO — percentuais de exemplo, referência 2026 -->
    <div class="suc" data-suc>

      <!-- ---------------------------------------------------- os controles -->
      <div class="suc-controls" data-rv data-d="1">

        <div class="suc-field suc-field-wealth">
          <label class="suc-label" for="suc-wealth">Patrimônio a transferir</label>
          <output class="suc-value" for="suc-wealth" aria-hidden="true" data-suc-out="wealth">R$ 8.000.000</output>
          <input class="suc-range" type="range" id="suc-wealth"
                 min="1000000" max="50000000" step="500000" value="8000000"
                 aria-valuetext="R$ 8.000.000" data-suc-wealth>
          <p class="suc-hint" aria-hidden="true"><span class="suc-hint-end">R$ 1 mi</span><span class="suc-hint-end">R$ 50 mi</span></p>
        </div>

        <div class="suc-field suc-field-state">
          <label class="suc-label" for="suc-state">Estado do inventário</label>
          <span class="suc-select-box">
            <select class="suc-select" id="suc-state" data-suc-state>
              <option value="SP" selected>São Paulo</option>
              <option value="RJ">Rio de Janeiro</option>
              <option value="MG">Minas Gerais</option>
              <option value="PR">Paraná</option>
              <option value="RS">Rio Grande do Sul</option>
              <option value="SC">Santa Catarina</option>
              <option value="BA">Bahia</option>
              <option value="DF">Distrito Federal</option>
            </select>
          </span>
          <p class="suc-hint suc-hint-solo">A alíquota de ITCMD é definida por cada estado.</p>
        </div>

        <div class="suc-field suc-field-heirs" role="group" aria-labelledby="suc-heirs-label">
          <span class="suc-label" id="suc-heirs-label">Herdeiros</span>
          <div class="suc-stepper">
            <button class="btn-disc suc-step" type="button" data-suc-heirs="-1"
                    aria-label="Um herdeiro a menos" aria-disabled="false">&#8722;</button>
            <span class="suc-stepper-read" aria-live="polite">
              <span class="suc-stepper-value" data-suc-out="heirs">3</span>
              <span class="suc-stepper-unit" data-suc-out="heirs-unit">herdeiros</span>
            </span>
            <button class="btn-disc suc-step" type="button" data-suc-heirs="1"
                    aria-label="Um herdeiro a mais" aria-disabled="false">+</button>
          </div>
          <p class="suc-hint suc-hint-solo">Mais herdeiros, inventário mais longo.</p>
        </div>

      </div>

      <!-- ------------------------------------------------- os dois cartões -->
      <div class="suc-cards" data-rv data-d="2">

        <article class="suc-card suc-card-raw">
          <header class="suc-card-head">
            <h3 class="suc-card-title">Sem planejamento</h3>
            <span class="pill">Inventário judicial</span>
          </header>

          <dl class="suc-lines">
            <div class="suc-line">
              <dt class="suc-line-label">ITCMD
                <span class="suc-line-note" data-suc-out="raw-rate">São Paulo, 4,0%</span></dt>
              <dd class="suc-line-value" data-suc-out="raw-tax">R$ 320.000</dd>
            </div>
            <div class="suc-line">
              <dt class="suc-line-label">Custas e emolumentos
                <span class="suc-line-note">1,0% do patrimônio</span></dt>
              <dd class="suc-line-value" data-suc-out="raw-fees">R$ 80.000</dd>
            </div>
            <div class="suc-line">
              <dt class="suc-line-label">Honorários advocatícios
                <span class="suc-line-note">6,0% do patrimônio</span></dt>
              <dd class="suc-line-value" data-suc-out="raw-legal">R$ 480.000</dd>
            </div>
          </dl>

          <p class="suc-total">
            <span class="suc-total-label">Custo estimado</span>
            <strong class="suc-total-value" data-suc-out="raw-total">R$ 880.000</strong>
            <span class="suc-flag">estimativa ilustrativa · muda por estado, por ano e por caso</span>
          </p>

          <div class="suc-bar" aria-hidden="true">
            <span class="suc-bar-fill suc-bar-raw" style="--suc-bar:.733" data-suc-bar="raw"></span>
          </div>

          <p class="suc-term">
            <span class="suc-term-label">Prazo estimado</span>
            <strong class="suc-term-value" data-suc-out="raw-term">de 16 a 36 meses</strong>
          </p>

          <p class="suc-say">O processo corre em juízo e o patrimônio fica indisponível até o fim.</p>
        </article>

        <article class="suc-card suc-card-plan">
          <header class="suc-card-head">
            <h3 class="suc-card-title">Com estrutura</h3>
            <span class="pill">Holding e doação em vida</span>
          </header>

          <dl class="suc-lines">
            <div class="suc-line">
              <dt class="suc-line-label">ITCMD
                <span class="suc-line-note" data-suc-out="plan-rate">antecipado na doação · São Paulo, 4,0%</span></dt>
              <dd class="suc-line-value" data-suc-out="plan-tax">R$ 320.000</dd>
            </div>
            <div class="suc-line">
              <dt class="suc-line-label">Constituição e registros
                <span class="suc-line-note">0,4% do patrimônio</span></dt>
              <dd class="suc-line-value" data-suc-out="plan-fees">R$ 32.000</dd>
            </div>
            <div class="suc-line">
              <dt class="suc-line-label">Honorários de estruturação
                <span class="suc-line-note">1,2% do patrimônio</span></dt>
              <dd class="suc-line-value" data-suc-out="plan-legal">R$ 96.000</dd>
            </div>
          </dl>

          <p class="suc-total">
            <span class="suc-total-label">Custo estimado</span>
            <strong class="suc-total-value" data-suc-out="plan-total">R$ 448.000</strong>
            <span class="suc-flag">estimativa ilustrativa · muda por estado, por ano e por caso</span>
          </p>

          <div class="suc-bar" aria-hidden="true">
            <span class="suc-bar-fill suc-bar-plan" style="--suc-bar:.373" data-suc-bar="plan"></span>
          </div>

          <p class="suc-term">
            <span class="suc-term-label">Prazo estimado</span>
            <strong class="suc-term-value" data-suc-out="plan-term">de 3 a 8 meses</strong>
          </p>

          <p class="suc-say">A holding não elimina o imposto: antecipa e organiza a transferência.</p>
        </article>

      </div>

      <!-- ------------------------------------------------------ a diferença -->
      <div class="suc-delta" data-rv data-d="3">
        <p class="suc-delta-line" aria-live="polite">Nesta simulação, a diferença de custo entre os dois caminhos é
          <strong class="suc-delta-value" data-suc-out="delta">R$ 432.000</strong>,
          <span class="suc-delta-term">e o prazo estimado passa de
          <span data-suc-out="delta-raw-term">16 a 36 meses</span> para
          <span data-suc-out="delta-plan-term">3 a 8 meses</span>.</span></p>
      </div>

      <p class="nota suc-nota" data-rv data-d="3">Simulação ilustrativa, para orientar a conversa —
        <strong class="suc-nota-strong">não é orientação jurídica ou tributária</strong> nem recomendação
        de investimento. Alíquotas de ITCMD, custas e honorários mudam por estado, por ano e por caso;
        os percentuais usados aqui são de referência <span data-suc-out="ano">2026</span>. Cada situação
        exige análise de advogado e contador.</p>

      <!-- O core esconde [data-rv] e .ttl (opacity:0) e só o motion.js os
           revela: sem JavaScript, a dobra inteira ficaria em branco — casca
           vazia, que é justamente o que não pode acontecer. Este <style> vive
           dentro do <noscript> porque é a única regra que o navegador só pode
           aplicar QUANDO NÃO HÁ script: um seletor equivalente em estilo.css
           piscaria (visível, depois escondido pelo core, depois revelado).
           Escopo no prefixo, para não desarmar o reveal do resto da página.
           A saída definitiva é do core — ver o gotcha e a ficha. -->
      <noscript>
        <style>
          .suc-head .ttl,
          .suc-head [data-rv],
          .suc [data-rv]{opacity:1;filter:none;transform:none}
        </style>
        <p class="nota suc-nota suc-nota-static">Com o JavaScript desativado, os cartões acima mostram um caso
          fixo — R$ 8.000.000, São Paulo, 3 herdeiros. Os controles continuam navegáveis, mas os números
          não recalculam.</p>
      </noscript>

      <div class="suc-cta" data-rv data-d="3">
        <a class="btn btn-primary" href="#diagnostico">Falar sobre a sua estrutura</a>
      </div>

    </div>
  </div>
</section>

estilo.css · 332 linhas · 12,7 KB

/* ==========================================================================
   Sucessão — calculadora de custo e prazo
   Prefixo: suc-
   Tokens: --accent --display --ease --fg --fg-2 --fg-muted --radius-media
           --radius-pill --rule --rule-2 --sans
   Primitivas do core (usadas, não recopiadas): .wrap .sec-pad .sec-head
   .eyebrow .sec-title .sec-lead .pill .btn .btn-primary .btn-disc

   AJUSTES PÚBLICOS — todos na raiz .suc, todos com unidade e padrão:
     --suc-gap        respiro entre painel de controles e cartões (px)
     --suc-card-pad   respiro interno do cartão (px)
     --suc-bar-h      altura da barra de custo (px, padrão 8px)
     --suc-thumb      diâmetro do polegar da faixa (px, padrão 20px)
     --suc-surface    cor do painel de controles (calculada a partir de --fg)
     --suc-line       cor da borda de painel e cartão (idem)
     --suc-mark       a cor de marca em FORMA — polegar, trilho, barra, borda
     --suc-mark-text  a mesma marca em TEXTO — só o número da diferença
   Os quatro saem de --fg, e não de um cinza fixo, para o componente
   sobreviver a qualquer escopo de tom: em .sec-paper viram um véu escuro
   sobre branco; em .sec-dark, um véu claro sobre preto. Não existe
   --light-rgb no core, então color-mix é o único caminho honesto aqui.
   ========================================================================== */

.suc{
  --suc-gap:clamp(22px,3vw,32px);
  --suc-card-pad:clamp(22px,2.6vw,34px);
  --suc-bar-h:8px;
  --suc-thumb:20px;
  --suc-surface:color-mix(in srgb,var(--fg) 3.5%,transparent);
  --suc-line:color-mix(in srgb,var(--fg) 13%,transparent);
  /* MEDIDO, e a razão de --accent nunca aparecer puro aqui: o sage da
     Liberta (#c6ceaa) contra o creme da dobra clara (#ede9e3) dá 1,32:1 —
     some. Em .sec-dark o mesmo sage dá 9:1 e brilha. Um componente que só
     vive no escuro pode usar --accent puro; este nasce em .sec-paper.
     A saída é misturar a marca com o TEXTO da própria seção, e não com
     transparente: escurece o sage sobre o creme e clareia o mesmo sage
     sobre o preto, sem o componente saber em qual escopo caiu.
     Duas dosagens medidas contra o creme: forma 50% -> 3,2:1 (o piso de
     1.4.11 para controle e gráfico); texto 45% -> 4,6:1 (o piso de 1.4.3).
     O <em> do título e o .pill continuam do core, e a peça não põe número
     nenhum neles. */
  --suc-mark:color-mix(in srgb,var(--accent) 50%,var(--fg));
  --suc-mark-text:color-mix(in srgb,var(--accent) 45%,var(--fg));
  margin-top:clamp(38px,5.5vh,62px);
}


/* ------------------------------------------------------------ controles
   Painel único para os três campos: eles pertencem à mesma pergunta, e
   soltos na coluna leem como três blocos sem relação. */
.suc-controls{
  display:grid;
  grid-template-columns:1.5fr 1fr 1fr;
  gap:var(--suc-gap);
  align-items:start;
  padding:var(--suc-card-pad);
  border:1px solid var(--suc-line);
  border-radius:var(--radius-media);
  background:var(--suc-surface);
}

.suc-label{
  display:block;
  font-family:var(--sans);font-weight:400;
  font-size:11.5px;line-height:1.4;letter-spacing:.16em;text-transform:uppercase;
  color:var(--fg-muted);
}

/* O valor grande é o mesmo desenho nos três campos — patrimônio, estado e
   herdeiros — para a fileira ler como uma linha só. */
.suc-value,
.suc-select,
.suc-stepper-value{
  font-family:var(--display);font-weight:700;
  letter-spacing:-.02em;color:var(--fg);
  font-variant-numeric:tabular-nums;
}
.suc-value{
  display:block;margin-top:12px;
  font-size:clamp(24px,2.6vw,32px);line-height:1.1;
}

/* --------------------------------------------------------- faixa (range)
   Altura de 20px na CAIXA e trilho de 3px pintado no ::track: a caixa é o
   alvo do ponteiro, o trilho é só a linha. Um input de 3px de altura tem
   alvo de clique de 3px — só o polegar é clicável. */
.suc-range{
  -webkit-appearance:none;appearance:none;
  display:block;width:100%;
  height:var(--suc-thumb);
  margin-top:16px;
  background:none;border:0;
  /* fração já preenchida do trilho; o script reescreve a cada input.
     14.3% = (8.000.000 − 1.000.000) / 49.000.000, o caso do markup. */
  --suc-fill:14.3%;
}
.suc-range::-webkit-slider-runnable-track{
  height:3px;border-radius:var(--radius-pill);
  background:linear-gradient(to right,
    var(--suc-mark) 0 var(--suc-fill),
    var(--rule-2) var(--suc-fill) 100%);
}
.suc-range::-moz-range-track{
  height:3px;border-radius:var(--radius-pill);
  background:linear-gradient(to right,
    var(--suc-mark) 0 var(--suc-fill),
    var(--rule-2) var(--suc-fill) 100%);
}
.suc-range::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:var(--suc-thumb);height:var(--suc-thumb);
  border:0;border-radius:50%;background:var(--suc-mark);
  margin-top:calc((3px - var(--suc-thumb)) / 2);
  transition:box-shadow .3s var(--ease);
}
.suc-range::-moz-range-thumb{
  width:var(--suc-thumb);height:var(--suc-thumb);
  border:0;border-radius:50%;background:var(--suc-mark);
  transition:box-shadow .3s var(--ease);
}
/* Halo no arrasto: cresce o alvo visual sem mexer no layout. */
.suc-range:hover::-webkit-slider-thumb,
.suc-range:active::-webkit-slider-thumb{
  box-shadow:0 0 0 7px color-mix(in srgb,var(--accent) 20%,transparent);
}
.suc-range:hover::-moz-range-thumb,
.suc-range:active::-moz-range-thumb{
  box-shadow:0 0 0 7px color-mix(in srgb,var(--accent) 20%,transparent);
}
@media(prefers-reduced-motion:reduce){
  .suc-range::-webkit-slider-thumb{transition:none}
  .suc-range::-moz-range-thumb{transition:none}
}

.suc-hint{
  display:flex;justify-content:space-between;gap:12px;
  margin-top:10px;
  font-size:11.5px;line-height:1.5;letter-spacing:.06em;
  color:var(--fg-muted);
}
/* Dica de uma frase só: volta a ser bloco, sem as duas pontas da régua. */
.suc-hint-solo{display:block;max-width:34ch}

/* --------------------------------------------------------------- estado
   <select> nativo, e não o conversao/select-custom: aqui a lista tem oito
   itens curtos e nenhum desenho por item, e o nativo entrega o teclado, a
   busca por letra e a roda do celular de graça. */
.suc-select-box{position:relative;display:block;margin-top:16px}
.suc-select{
  -webkit-appearance:none;appearance:none;
  width:100%;font-size:17px;line-height:1.35;
  padding:0 30px 12px 0;
  background:none;border:0;border-bottom:1px solid var(--rule-2);border-radius:0;
  cursor:pointer;
  transition:border-color .3s var(--ease);
}
.suc-select:hover{border-bottom-color:var(--suc-mark)}
/* Seta desenhada com duas bordas, e não imagem: herda a cor do texto de
   apoio e acompanha o tema sem um SVG com cor crua embutida. */
.suc-select-box::after{
  content:"";position:absolute;right:7px;top:7px;
  width:8px;height:8px;
  border-right:1px solid var(--fg-muted);
  border-bottom:1px solid var(--fg-muted);
  transform:rotate(45deg);
  pointer-events:none;
}

/* ------------------------------------------------------------ herdeiros */
.suc-stepper{display:flex;align-items:center;gap:14px;margin-top:12px}
.suc-step{font-family:var(--display);font-weight:700;font-size:19px;line-height:1}
.suc-stepper-read{display:flex;flex-direction:column;line-height:1.05}
.suc-stepper-value{font-size:clamp(24px,2.6vw,32px)}
.suc-stepper-unit{
  margin-top:4px;
  font-size:11.5px;line-height:1.4;letter-spacing:.14em;text-transform:uppercase;
  color:var(--fg-muted);
}

/* ------------------------------------------------------- os dois cartões */
.suc-cards{
  display:grid;grid-template-columns:1fr 1fr;
  gap:var(--suc-gap);margin-top:var(--suc-gap);
}
.suc-card{
  display:flex;flex-direction:column;
  padding:var(--suc-card-pad);
  border:1px solid var(--suc-line);
  border-radius:var(--radius-media);
}
/* O caminho estruturado é o único marcado na cor de marca — borda e barra.
   O outro fica em neutro: carregar o inventário na cor da casa inverteria
   a leitura. */
.suc-card-plan{border-color:var(--suc-mark)}

.suc-card-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px}
.suc-card-title{
  font-family:var(--display);font-weight:700;
  font-size:19px;line-height:1.2;letter-spacing:-.02em;color:var(--fg);
}

/* ---------------------------------------------------------- as parcelas
   Cada parcela com o percentual ao lado do nome. Soma que não mostra as
   parcelas é chute com aparência de conta — e num número que fala de
   imposto isso não é detalhe de estilo. */
.suc-lines{margin:22px 0 0;display:grid;gap:0}
.suc-line{
  display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  padding:12px 0;
  border-bottom:1px solid var(--rule);
}
.suc-line-label{margin:0;font-size:14px;line-height:1.4;color:var(--fg-2)}
.suc-line-note{
  display:block;margin-top:4px;
  font-size:11.5px;line-height:1.4;letter-spacing:.05em;
  color:var(--fg-muted);
}
.suc-line-value{
  margin:0;white-space:nowrap;
  font-family:var(--display);font-weight:700;
  font-size:15px;letter-spacing:-.01em;color:var(--fg);
  font-variant-numeric:tabular-nums;
}

/* margin-top:auto encosta o bloco do total no pé do cartão: os dois cartões
   têm alturas de cabeçalho diferentes (a pílula da direita quebra em duas
   linhas antes), e sem isto os dois totais não ficariam na mesma altura. */
.suc-total{margin-top:auto;padding-top:22px}
.suc-total-label{
  display:block;
  font-size:11.5px;line-height:1.4;letter-spacing:.16em;text-transform:uppercase;
  color:var(--fg-muted);
}
.suc-total-value{
  display:block;margin-top:6px;
  font-family:var(--display);font-weight:700;
  font-size:clamp(28px,3.2vw,40px);line-height:1.05;letter-spacing:-.025em;
  color:var(--fg);font-variant-numeric:tabular-nums;
}
/* A ressalva colada no número, não no rodapé: é aqui que a pessoa lê. */
.suc-flag{
  display:block;margin-top:8px;
  font-size:11px;line-height:1.45;letter-spacing:.04em;
  color:var(--fg-muted);
}

/* ------------------------------------------------------------- a barra
   Decorativa (aria-hidden): quem usa leitor de tela recebe os números do
   .suc-total e do .suc-term, não "gráfico". O raio fica no TRILHO, com
   overflow:hidden — no preenchimento, scaleX esmagaria a ponta em elipse. */
.suc-bar{
  height:var(--suc-bar-h);margin-top:18px;
  border-radius:var(--radius-pill);overflow:hidden;
  background:color-mix(in srgb,var(--fg) 9%,transparent);
}
.suc-bar-fill{
  display:block;width:100%;height:100%;
  transform-origin:left center;
  transform:scaleX(var(--suc-bar,1));
  transition:transform .55s var(--ease);
}
.suc-bar-raw{background:color-mix(in srgb,var(--fg) 55%,transparent)}
.suc-bar-plan{background:var(--suc-mark)}
@media(prefers-reduced-motion:reduce){
  .suc-bar-fill{transition:none}
}

.suc-term{
  display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  margin-top:16px;
}
.suc-term-label{
  font-size:11.5px;line-height:1.4;letter-spacing:.16em;text-transform:uppercase;
  color:var(--fg-muted);
}
.suc-term-value{
  font-family:var(--display);font-weight:700;
  font-size:15px;letter-spacing:-.01em;color:var(--fg);white-space:nowrap;
}
.suc-say{
  margin-top:16px;max-width:46ch;
  font-size:13.5px;line-height:1.55;color:var(--fg-muted);
}

/* ---------------------------------------------------------- a diferença */
.suc-delta{
  margin-top:var(--suc-gap);
  padding-top:clamp(22px,3vh,30px);
  border-top:1px solid var(--suc-line);
}
.suc-delta-line{
  max-width:72ch;
  font-size:clamp(17px,1.7vw,21px);line-height:1.55;color:var(--fg);
}
.suc-delta-value{
  font-family:var(--display);font-weight:700;
  font-size:1.35em;letter-spacing:-.02em;color:var(--suc-mark-text);
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
}
.suc-delta-term{color:var(--fg-2)}

/* ---------------------------------------------------------- a ressalva */
/* Corpo, entrelinha e tinta vêm da primitiva .nota do core. Fica a posição e a
   medida: 88ch porque esta ressalva é longa e acompanha a largura dos dois
   cartões de cenário — em 74ch ela viraria um bloco estreito no meio deles. */
.suc-nota{
  margin-top:20px;
  --nota-max:88ch;
}
.suc-nota-strong{font-weight:600;color:var(--fg-2)}
.suc-nota-static{margin-top:14px}

.suc-cta{margin-top:clamp(26px,3.5vh,38px)}

/* ============================================================== mobile */
@media(max-width:980px){
  .suc-controls{grid-template-columns:1fr 1fr}
  .suc-field-wealth{grid-column:1 / -1}
  .suc-cards{grid-template-columns:1fr}
  /* Empilhados, os dois totais já não se comparam lado a lado: o
     margin-top:auto só gastaria respiro no fim do primeiro cartão. */
  .suc-total{margin-top:0}
}

@media(max-width:620px){
  .suc-controls{grid-template-columns:1fr;gap:26px}
  .suc-field-wealth{grid-column:auto}
  .suc-line{flex-direction:column;align-items:flex-start;gap:6px}
  .suc-term{flex-direction:column;align-items:flex-start;gap:4px}
  .suc-delta-value{white-space:normal}
}

script.js · 259 linhas · 11,2 KB

/* ==========================================================================
   Sucessão — calculadora de custo e prazo
   Prefixo: suc-

   Lê os três controles, recalcula os dois cartões e escreve o resultado no
   texto que já está no HTML. Sem lib, sem gráfico em canvas: a única forma
   é a barra, movida por scaleX numa custom property.

   O que este script NÃO faz, de propósito: não guarda nada em localStorage,
   não coleta nome, e-mail ou telefone e não manda nada para lugar nenhum.
   São dados patrimoniais de alguém; a captura é do
   conversao/cta-formulario-lead, para onde o botão final aponta.
   ========================================================================== */

(function () {
  var raizes = document.querySelectorAll('[data-suc]');
  if (!raizes.length) return;

  /* ======================================================================
     TABELA ILUSTRATIVA — REFERÊNCIA 2026
     ----------------------------------------------------------------------
     ATENÇÃO, quem for publicar: estes percentuais são de EXEMPLO, para dar
     ordem de grandeza a uma conversa. Não são tabela oficial.

     · O ITCMD é estadual: cada unidade da federação fixa a sua alíquota, e
       várias delas são PROGRESSIVAS por faixa de valor. O número abaixo é o
       topo da progressiva onde ela existe — a leitura de patrimônio alto.
     · Custas, emolumentos e honorários mudam por comarca, por tabela da
       seccional da OAB e por complexidade do caso. Tabelas reais de
       honorários costumam ser REGRESSIVAS (o percentual cai conforme o
       espólio cresce); aqui são lineares, para a conta ficar auditável
       linha a linha.
     · Antes de ir ao ar, troque tudo isto pela tabela conferida com o
       jurídico e o contábil do cliente, e atualize o ano na ressalva do
       markup e no rótulo ANO_REF.
     ====================================================================== */
  var ANO_REF = '2026';

  var ESTADOS = {
    SP: { nome: 'São Paulo',          itcmd: 4.0 },
    RJ: { nome: 'Rio de Janeiro',     itcmd: 8.0 },
    MG: { nome: 'Minas Gerais',       itcmd: 5.0 },
    PR: { nome: 'Paraná',             itcmd: 4.0 },
    RS: { nome: 'Rio Grande do Sul',  itcmd: 6.0 },
    SC: { nome: 'Santa Catarina',     itcmd: 8.0 },
    BA: { nome: 'Bahia',              itcmd: 8.0 },
    DF: { nome: 'Distrito Federal',   itcmd: 6.0 }
  };

  /* Percentuais sobre o patrimônio transferido. */
  var SEM = { custas: 1.0, honorarios: 6.0 };   /* inventário judicial */
  var COM = { custas: 0.4, honorarios: 1.2 };   /* holding + doação em vida */

  /* Prazo em MESES, sempre faixa. Mais herdeiros, inventário mais longo:
     é o número de acordos a fechar, não o valor, que estica o processo. */
  function prazoSem(h) { return [12 + (h - 1) * 2, 24 + (h - 1) * 6]; }
  function prazoCom(h) { return [3, 6 + (h - 1)]; }

  var HERD_MIN = 1, HERD_MAX = 6;
  var DUR_CONTA = 380;   /* ms da contagem da diferença */

  /* Teto da tabela — usado SÓ como escala das duas barras, para que as duas
     se movam quando o estado muda. Se a escala fosse o total do cartão da
     esquerda, ele ficaria cheio para sempre e a troca de estado não
     apareceria em forma nenhuma. */
  var TETO = 0;
  for (var uf in ESTADOS) {
    if (ESTADOS.hasOwnProperty(uf) && ESTADOS[uf].itcmd > TETO) TETO = ESTADOS[uf].itcmd;
  }
  var ESCALA_PCT = TETO + SEM.custas + SEM.honorarios;

  /* ---------------------------------------------------------- formatação */
  function moeda(v) {
    var s = String(Math.round(v)), saida = '', c = 0;
    for (var i = s.length - 1; i >= 0; i--) {
      saida = s.charAt(i) + saida;
      c++;
      if (c % 3 === 0 && i > 0) saida = '.' + saida;
    }
    return 'R$ ' + saida;
  }
  function pct(v) { return v.toFixed(1).replace('.', ',') + '%'; }
  function faixa(par) { return 'de ' + par[0] + ' a ' + par[1] + ' meses'; }
  /* A mesma faixa sem o "de" inicial: na frase da diferença ele já veio no
     "passa DE ... para ...", e repetido sairia "para de 3 a 8 meses". */
  function faixaCurta(par) { return par[0] + ' a ' + par[1] + ' meses'; }

  function reduzido() {
    if (window.LIB && typeof window.LIB.reduce === 'boolean') return window.LIB.reduce;
    return matchMedia('(prefers-reduced-motion: reduce)').matches &&
           location.search.indexOf('motion=on') < 0;
  }

  /* ---------------------------------------------------------------- init */
  function iniciar(raiz) {
    var range = raiz.querySelector('[data-suc-wealth]');
    var select = raiz.querySelector('[data-suc-state]');
    var passos = raiz.querySelectorAll('[data-suc-heirs]');
    var barras = raiz.querySelectorAll('[data-suc-bar]');
    if (!range || !select) return;

    /* Mapa das saídas de texto, montado uma vez: o render não volta a
       consultar o DOM. */
    var saida = {};
    var alvos = raiz.querySelectorAll('[data-suc-out]');
    for (var i = 0; i < alvos.length; i++) {
      saida[alvos[i].getAttribute('data-suc-out')] = alvos[i];
    }
    var barra = {};
    for (var b = 0; b < barras.length; b++) {
      barra[barras[b].getAttribute('data-suc-bar')] = barras[b];
    }

    /* Estado em MEMÓRIA, e só. Morre no recarregamento — de propósito. */
    var est = {
      wealth: parseFloat(range.value) || 8000000,
      uf: select.value || 'SP',
      herdeiros: parseInt((saida.heirs && saida.heirs.textContent) || '3', 10) || 3,
      /* mostrado = o número que está PINTADO na tela, não o alvo. É o que
         permite a contagem recomeçar de onde o olho parou em vez de saltar
         para trás a cada quadro de arrasto. null na largada: o markup já
         traz o valor certo e a primeira passada não conta nada. */
      mostrado: null,
      raf: 0
    };

    function escrever(chave, texto) {
      var el = saida[chave];
      if (el && el.textContent !== texto) el.textContent = texto;
    }

    /* ------------------------------------------------------- a contagem
       Só a diferença conta; os totais dos cartões trocam secos, porque são
       seis números ao mesmo tempo e seis contagens simultâneas viram ruído.

       A contagem parte de est.mostrado — o valor PINTADO —, e não do alvo
       anterior. Reiniciada a cada quadro de arrasto, ela vira um filtro:
       cada quadro anda ~12% do que falta, o número persegue o polegar com
       ~130ms de atraso e assenta sozinho quando o arrasto para. Partindo do
       alvo anterior, cada quadro daria um salto para trás. */
    function contar(el, para) {
      if (est.raf) { cancelAnimationFrame(est.raf); est.raf = 0; }
      /* Movimento reduzido: o número aparece no valor final, sem contagem.
         Idem na primeira passada, quando não há de onde partir. */
      if (est.mostrado === null || reduzido()) {
        est.mostrado = para;
        el.textContent = moeda(para);
        return;
      }
      var de = est.mostrado, t0 = 0;
      function passo(agora) {
        if (!t0) t0 = agora;
        var t = Math.min(1, (agora - t0) / DUR_CONTA);
        var e = 1 - Math.pow(1 - t, 3);
        est.mostrado = de + (para - de) * e;
        el.textContent = moeda(est.mostrado);
        /* O laço dorme assim que chega ao fim: nada roda em repouso. */
        if (t < 1) { est.raf = requestAnimationFrame(passo); }
        else { est.mostrado = para; est.raf = 0; }
      }
      est.raf = requestAnimationFrame(passo);
    }

    /* --------------------------------------------------------- o cálculo */
    function render() {
      var p = est.wealth;
      var uf = ESTADOS[est.uf] ? est.uf : 'SP';
      var e = ESTADOS[uf];
      var h = est.herdeiros;

      var imposto = p * e.itcmd / 100;             /* o MESMO nos dois lados */
      var semCustas = p * SEM.custas / 100;
      var semHon = p * SEM.honorarios / 100;
      var semTotal = imposto + semCustas + semHon;

      var comCustas = p * COM.custas / 100;
      var comHon = p * COM.honorarios / 100;
      var comTotal = imposto + comCustas + comHon;

      var pSem = prazoSem(h), pCom = prazoCom(h);

      /* faixa (range) */
      var min = parseFloat(range.min), max = parseFloat(range.max);
      range.style.setProperty('--suc-fill', ((p - min) / (max - min) * 100).toFixed(1) + '%');
      range.setAttribute('aria-valuetext', moeda(p));
      escrever('wealth', moeda(p));

      /* herdeiros */
      escrever('heirs', String(h));
      escrever('heirs-unit', h === 1 ? 'herdeiro' : 'herdeiros');
      for (var k = 0; k < passos.length; k++) {
        var d = parseInt(passos[k].getAttribute('data-suc-heirs'), 10);
        var trava = (d < 0 && h <= HERD_MIN) || (d > 0 && h >= HERD_MAX);
        /* aria-disabled e não disabled: um botão desabilitado perde o foco
           que já estava nele, e o teclado cai fora do grupo no meio do uso.
           O core desenha o estado apagado para os dois. */
        passos[k].setAttribute('aria-disabled', trava ? 'true' : 'false');
      }

      /* cartão sem planejamento */
      escrever('raw-rate', e.nome + ', ' + pct(e.itcmd));
      escrever('raw-tax', moeda(imposto));
      escrever('raw-fees', moeda(semCustas));
      escrever('raw-legal', moeda(semHon));
      escrever('raw-total', moeda(semTotal));
      escrever('raw-term', faixa(pSem));

      /* cartão com estrutura — a linha de ITCMD repete o MESMO valor, e o
         rótulo diz "antecipado na doação". Holding não elimina imposto;
         apagar esta linha faria a peça prometer isenção. */
      escrever('plan-rate', 'antecipado na doação · ' + e.nome + ', ' + pct(e.itcmd));
      escrever('plan-tax', moeda(imposto));
      escrever('plan-fees', moeda(comCustas));
      escrever('plan-legal', moeda(comHon));
      escrever('plan-total', moeda(comTotal));
      escrever('plan-term', faixa(pCom));

      /* as barras */
      var escala = p * ESCALA_PCT / 100;
      if (barra.raw) barra.raw.style.setProperty('--suc-bar', (semTotal / escala).toFixed(3));
      if (barra.plan) barra.plan.style.setProperty('--suc-bar', (comTotal / escala).toFixed(3));

      /* a diferença — subtração dos dois totais, nunca "economia" */
      var dif = semTotal - comTotal;
      if (saida.delta) contar(saida.delta, dif);
      escrever('delta-raw-term', faixaCurta(pSem));
      escrever('delta-plan-term', faixaCurta(pCom));
      escrever('ano', ANO_REF);
    }

    /* ------------------------------------------------------- os controles */
    range.addEventListener('input', function () {
      est.wealth = parseFloat(range.value);
      render();
    });

    select.addEventListener('change', function () {
      est.uf = select.value;
      render();
    });

    for (var j = 0; j < passos.length; j++) {
      passos[j].addEventListener('click', function () {
        var d = parseInt(this.getAttribute('data-suc-heirs'), 10);
        var novo = Math.min(HERD_MAX, Math.max(HERD_MIN, est.herdeiros + d));
        if (novo === est.herdeiros) return;
        est.herdeiros = novo;
        render();
      });
    }

    /* Primeira passada sem contagem: o HTML já traz o caso inicial escrito,
       e este render só confirma que o texto e o modelo dizem o mesmo. Se um
       dia divergirem, o modelo vence — e o preview mostra a divergência. */
    render();
  }

  for (var r = 0; r < raizes.length; r++) iniciar(raizes[r]);
})();

Dobra clara com três controles numa fileira — patrimônio em faixa, estado em select, herdeiros em passo-a-passo — e dois cartões que se recalculam lado a lado: inventário judicial contra holding familiar com doação em vida. Cada cartão soma parcelas visíveis (ITCMD, custas, honorários), mostra prazo em faixa e uma barra horizontal em scaleX, as duas na mesma escala. Abaixo, a diferença em destaque, contando quando muda.

O problema editorial é o de sempre em sucessão: ninguém age por um argumento abstrato sobre organização, mas quase todo mundo reage a uma ordem de grandeza. A peça troca o discurso por uma conta que a pessoa mesma monta — e por isso a conta tem de aparecer inteira, parcela a parcela. Soma que não mostra as parcelas é chute com aparência de conta, e num número que fala de imposto isso deixa de ser detalhe de estilo.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Olho Uma palavra — o assunto, não a promessa. "Sucessão".
Título Até 6 palavras. O <em> marca a palavra que inverte o sentido ("não"), nunca um adjetivo.
Apoio Duas linhas, ~30 palavras. Tem de dizer que os dois caminhos têm custo — é o que impede a dobra de ler como propaganda de holding.
Rótulo de controle Substantivo, 2 a 4 palavras, caixa alta.
Dica sob o controle Uma frase, teto de 34ch. Explica o parâmetro, não vende.
Nome do cartão 2 ou 3 palavras, sem adjetivo de valor: "Sem planejamento", não "O caminho caro".
Pílula O nome do instrumento jurídico, não um slogan.
Parcela Nome da despesa + o percentual visível ao lado. Parcela sem percentual não entra.
Frase do cartão Uma frase, até 15 palavras. A do cartão estruturado tem de dizer o que a estrutura não faz.
Ressalva Texto fixo. Encurtar é o único jeito de errar aqui.
Botão Verbo + o assunto da conversa. Nunca "simular de novo".

Especificação dos assets

Nenhum. Sem foto, sem ícone, sem SVG de biblioteca. É deliberado: número que fala de imposto e de morte sobre foto de família feliz é exatamente o registro a evitar. As duas barras são <span> com transform:scaleX(), e não SVG, porque não há eixo, série nem caminho a desenhar — o dado é o texto do cartão, e as barras levam aria-hidden="true".


Compliance

Esta é a parte cara da peça. O que o consumidor tem de fazer antes de publicar:

  1. Trocar a tabela. ESTADOS, SEM e COM, no topo do script.js, são ilustrativos, referência 2026. O ITCMD é estadual e várias unidades da federação são progressivas — o número ali é o topo da progressiva. Custas, emolumentos e honorários mudam por comarca, por tabela da seccional da OAB e por complexidade. Tabelas reais de honorários costumam ser regressivas; as daqui são lineares, para a conta ficar auditável linha a linha.
  2. Atualizar o ano na constante ANO_REF do script (o markup lê a mesma constante em [data-suc-out="ano"], então os dois não divergem).
  3. Passar pela área de compliance e pelo jurídico do cliente, com a tabela final, antes de ir ao ar.
  4. Não mexer na ressalva. Ela diz, com estas palavras: simulação ilustrativa, não é orientação jurídica ou tributária, alíquotas e custas mudam por estado e por ano, cada caso exige análise de advogado e contador. Fora dela, cada total carrega um .suc-flag colado ao número — "estimativa ilustrativa · muda por estado, por ano e por caso" —, porque a ressalva tem de estar onde a pessoa lê o número, não no rodapé.

O que a peça já garante e não pode ser desfeito na adaptação:


Como usar

<link rel="stylesheet" href="componentes/conversao/calculadora-sucessao/estilo.css">
<script src="componentes/conversao/calculadora-sucessao/script.js"></script>

Os id do markup (suc-wealth, suc-state, suc-heirs-label) existem porque for e aria-labelledby exigem. O botão final aponta para #diagnostico: troque pela âncora do formulário do projeto.

Ganchos

[data-suc] a raiz · [data-suc-wealth] a faixa (min/max/step no markup) · [data-suc-state] o select · [data-suc-heirs="-1|1"] os dois botões · [data-suc-out="..."] as dezenove saídas de texto · [data-suc-bar="raw|plan"] as duas barras. Variáveis: --suc-gap --suc-card-pad --suc-bar-h --suc-thumb --suc-surface --suc-line --suc-mark --suc-mark-text na raiz, --suc-bar em cada barra, --suc-fill na faixa.

Variações


Gotchas


Histórico

Projeto Onde Data Diferenças
autoral construído do zero na biblioteca 2026-09-03 —

Origem. Autoral, sem referência única e sem uma linha copiada. O padrão — comparar dois caminhos com as parcelas à vista — aparece em três famílias: a calculadora de custo de inventário dos escritórios de advocacia sucessória, o comparador "antes e depois" das gestoras de patrimônio e o simulador de custo de portabilidade dos bancos. Registro consolidado em projetos/autorais.md.

Decisões de tradução para o contrato. O gráfico virou duas barras em scaleX numa custom property, e não SVG, porque não há eixo, série nem caminho a desenhar. O ITCMD ficou nos dois cartões, com o mesmo valor, por compliance antes de por desenho. O prazo virou faixa. A contagem só existe num número, o da diferença — seis contagens simultâneas viram ruído. O estado vive em memória, sem localStorage. E a cor de marca entra sempre misturada com --fg, nunca pura, porque a dobra é clara.

Medições (Chrome headless, bin/medir.js). Desktop 1440×900: dobra 1602px, overflowX ok, console limpo, cartões 532×559 com os dois totais no mesmo top (930). Tablet 820: controles em duas colunas, patrimônio ocupando a fileira inteira (677px), cartões empilhados. Mobile 390: dobra 2676px, uma coluna, cartões 350×656, overflowX ok. Teclado: os quatro controles respondem a seta, Enter e Espaço, com :focus-visible em solid 2px offset 3px nos quatro. --reduce: transição da barra em 0s, filter:none no [data-rv], diferença escrita no valor final no mesmo tique. Aritmética conferida nos oito estados: parcelas somando o total exibido e diferença igual à subtração.