> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morada.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a simulator to explore scenarios

> Turn a repeated calculation into a simple tool: change the values and see the result immediately.

export const miniappArtifact = {
  filename: "simulador-cenarios-comerciais.html",
  sha256: "d8263baa768a87775e6d8c5b0ccf6a08f7a9bd0ba4df7f5ae94e0c3c845114ed",
  html: ["<!doctype html>\n<html lang=\"pt-BR\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n  <meta name=\"theme-color\" content=\"#123c43\">\n  <title>Simulador de cenários comerciais</title>\n  <style>\n    :root {\n      --bg: #f3f7f6;\n      --surface: #ffffff;\n      --ink: #163238;\n      --muted: #60767a;\n      --brand: #087c75;\n      --brand-dark: #075c58;\n      --accent: #dff3ef;\n      --border: #d8e5e3;\n      --warning-bg: #fff6d9;\n      --warning-ink: #6c5200;\n      --focus: #ffbd2e;\n      --radius: 18px;\n      --shadow: 0 10px 30px rgba(18, 60, 67, 0.10);\n    }\n\n    * { box-sizing: border-box; }\n\n    html { color-scheme: light; }\n\n    body {\n      margin: 0;\n      min-height: 100vh;\n      background: var(--bg);\n      color: var(--ink);\n      font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      line-height: 1.45;\n    }\n\n    button, input { font: inherit; }\n\n    .page {\n      width: min(100%, 760px);\n      margin: 0 auto;\n      padding: max(20px, env(safe-area-inset-top)) 16px max(28px, env(safe-area-inset-bottom));\n    }\n\n    header { margin-bottom: 18px; }\n\n    .eyebrow {\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      margin: 0 0 10px;\n      padding: 7px 10px;\n      border-radius: 999px;\n      background: var(--warning-bg);\n      color: var(--warning-ink);\n      font-size: 0.78rem;\n      font-weight: 800;\n      letter-spacing: 0.05em;\n      text-transform: uppercase;\n    }\n\n    .eyebrow::before {\n      content: \"\";\n      width: 8px;\n      height: 8px;\n      border-radius: 50%;\n      background: #d39d00;\n    }\n\n    h1 {\n      margin: 0;\n      max-width: 650px;\n      font-size: clamp(1.8rem, 7vw, 2.7rem);\n      line-height: 1.08;\n      letter-spacing: -0.035em;\n    }\n\n    .intro {\n      margin: 10px 0 0;\n      max-width: 620px;\n      color: var(--muted);\n      font-size: 1rem;\n    }\n\n    .simulator {\n      overflow: hidden;\n      border: 1px solid var(--border);\n      border-radius: var(--radius);\n      background: var(--surface);\n      box-shadow: var(--shadow);\n    }\n\n    .controls {\n      display: grid;\n      gap: 0;\n    }\n\n    .control {\n      padding: 22px 20px;\n      border-bottom: 1px solid var(--border);\n    }\n\n    .control-head {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 16px;\n      margin-bottom: 16px;\n    }\n\n    .control label {\n      font-size: 1rem;\n      font-weight: 750;\n    }\n\n    .number-wrap {\n      position: relative;\n      flex: 0 0 auto;\n    }\n\n    input[type=\"number\"] {\n      width: 112px;\n      min-height: 48px;\n      padding: 9px 34px 9px 10px;\n      border: 2px solid var(--border);\n      border-radius: 12px;\n      background: #fbfdfd;\n      color: var(--ink);\n      font-size: 1.05rem;\n      font-weight: 750;\n      text-align: right;\n    }\n\n    .number-wrap.leads input { padding-right: 12px; }\n\n    .unit {\n      position: absolute;\n      top: 50%;\n      right: 12px;\n      transform: translateY(-50%);\n      color: var(--muted);\n      font-weight: 750;\n      pointer-events: none;\n    }\n\n    input[type=\"range\"] {\n      width: 100%;\n      height: 44px;\n      margin: 0;\n      background: transparent;\n      accent-color: var(--brand);\n      cursor: pointer;\n    }\n\n    input[type=\"range\"]::-webkit-slider-runnable-track {\n      height: 9px;\n      border-radius: 999px;\n      background: var(--accent);\n    }\n\n    input[type=\"range\"]::-webkit-slider-thumb {\n      width: 28px;\n      height: 28px;\n      margin-top: -9.5px;\n      border: 3px solid white;\n      border-radius: 50%;\n      background: var(--brand);\n      box-shadow: 0 2px 8px rgba(7, 92, 88, 0.35);\n      -webkit-appearance: none;\n    }\n\n    input[type=\"range\"]::-moz-range-track {\n      height: 9px;\n      border-radius: 999px;\n      background: var(--accent);\n    }\n\n    input[type=\"range\"]::-moz-range-thumb {\n      width: 24px;\n      height: 24px;\n      border: 3px solid ", "white;\n      border-radius: 50%;\n      background: var(--brand);\n      box-shadow: 0 2px 8px rgba(7, 92, 88, 0.35);\n    }\n\n    input:focus-visible, button:focus-visible {\n      outline: 3px solid var(--focus);\n      outline-offset: 3px;\n    }\n\n    .range-labels {\n      display: flex;\n      justify-content: space-between;\n      margin-top: -3px;\n      color: var(--muted);\n      font-size: 0.78rem;\n      font-variant-numeric: tabular-nums;\n    }\n\n    .result {\n      padding: 24px 20px;\n      background: #123c43;\n      color: white;\n      text-align: center;\n    }\n\n    .result-label {\n      margin: 0 0 3px;\n      color: #bfe4df;\n      font-size: 0.9rem;\n      font-weight: 750;\n      letter-spacing: 0.04em;\n      text-transform: uppercase;\n    }\n\n    .result-value {\n      margin: 0;\n      font-size: clamp(3.25rem, 15vw, 5.7rem);\n      font-weight: 850;\n      line-height: 1;\n      letter-spacing: -0.055em;\n      font-variant-numeric: tabular-nums;\n    }\n\n    .result-unit {\n      display: block;\n      margin-top: 5px;\n      color: #d8eeeb;\n      font-size: 1rem;\n      font-weight: 700;\n    }\n\n    .formula {\n      width: fit-content;\n      max-width: 100%;\n      margin: 18px auto 0;\n      padding: 11px 14px;\n      border: 1px solid rgba(255,255,255,0.18);\n      border-radius: 12px;\n      background: rgba(255,255,255,0.08);\n      color: #eef8f7;\n      font-size: 0.94rem;\n      font-variant-numeric: tabular-nums;\n    }\n\n    .formula strong { color: white; }\n\n    .actions {\n      display: flex;\n      justify-content: center;\n      padding: 18px 20px 20px;\n      background: var(--surface);\n    }\n\n    .reset {\n      min-width: 170px;\n      min-height: 48px;\n      padding: 11px 18px;\n      border: 2px solid var(--brand);\n      border-radius: 12px;\n      background: white;\n      color: var(--brand-dark);\n      font-weight: 800;\n      cursor: pointer;\n    }\n\n    .reset:hover { background: var(--accent); }\n    .reset:active { transform: translateY(1px); }\n\n    .note {\n      margin: 16px 2px 0;\n      padding: 14px 16px;\n      border-left: 4px solid #d39d00;\n      border-radius: 0 10px 10px 0;\n      background: var(--warning-bg);\n      color: var(--warning-ink);\n      font-size: 0.88rem;\n    }\n\n    .note strong { font-weight: 850; }\n\n    @media (min-width: 640px) {\n      .page { padding-left: 24px; padding-right: 24px; }\n      .controls { grid-template-columns: 1fr 1fr; }\n      .control { padding: 26px; }\n      .control:first-child { border-right: 1px solid var(--border); }\n      .result { padding: 30px; }\n    }\n\n    @media (max-width: 420px) {\n      .control-head { align-items: flex-start; flex-direction: column; gap: 10px; }\n      .number-wrap, input[type=\"number\"] { width: 100%; }\n      input[type=\"number\"] { text-align: left; }\n      .actions { padding-bottom: 18px; }\n      .reset { width: 100%; }\n    }\n\n    @media (prefers-reduced-motion: no-preference) {\n      .result-value { transition: transform 120ms ease; }\n      .result-value.pulse { transform: scale(1.025); }\n    }\n  </style>\n</head>\n<body>\n  <main class=\"page\">\n    <header>\n      <p class=\"eyebrow\">Simulação — não é previsão</p>\n      <h1>Simulador de cenários comerciais</h1>\n      <p class=\"intro\">Ajuste as entradas para explorar o impacto de volume e conversão nas vendas do mês.</p>\n    </header>\n\n    <section class=\"simulator\" aria-labelledby=\"simulator-title\">\n      <h2 id=\"simulator-title\" style=\"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;\">Controles da simulação</h2>\n\n      <div class=\"controls\">\n        <div class=\"control\">\n          <div class=\"control-head\">\n            <label for=\"leadsNumber\">Leads no mês</label>\n            <div class=\"number-wrap leads\">\n              <input id=\"leadsNumber\" type=\"number\" inputmode=\"numeric\" min=\"0\" max=\"1000\" step=\"1\" value=\"400\" aria-describedby=\"leadsLimits\">\n            </div>\n          </div>\n          <input id=\"leadsRange\" type=\"range\" min=\"0\" max=", "\"1000\" step=\"1\" value=\"400\" aria-label=\"Leads no mês, de zero a mil\">\n          <div class=\"range-labels\" id=\"leadsLimits\"><span>0</span><span>1.000</span></div>\n        </div>\n\n        <div class=\"control\">\n          <div class=\"control-head\">\n            <label for=\"conversionNumber\">Taxa de conversão</label>\n            <div class=\"number-wrap\">\n              <input id=\"conversionNumber\" type=\"number\" inputmode=\"decimal\" min=\"0\" max=\"10\" step=\"0.1\" value=\"2\" aria-describedby=\"conversionLimits\">\n              <span class=\"unit\" aria-hidden=\"true\">%</span>\n            </div>\n          </div>\n          <input id=\"conversionRange\" type=\"range\" min=\"0\" max=\"10\" step=\"0.1\" value=\"2\" aria-label=\"Taxa de conversão, de zero a dez por cento\">\n          <div class=\"range-labels\" id=\"conversionLimits\"><span>0%</span><span>10%</span></div>\n        </div>\n      </div>\n\n      <div class=\"result\" aria-live=\"polite\" aria-atomic=\"true\">\n        <p class=\"result-label\">Estimativa da simulação</p>\n        <p class=\"result-value\" id=\"salesResult\">8</p>\n        <span class=\"result-unit\" id=\"salesUnit\">vendas</span>\n        <div class=\"formula\" id=\"formula\"><strong>Conta:</strong> 400 leads × 2% = 8 vendas</div>\n      </div>\n\n      <div class=\"actions\">\n        <button class=\"reset\" id=\"resetButton\" type=\"button\">Recomeçar</button>\n      </div>\n    </section>\n\n    <aside class=\"note\">\n      <strong>Importante:</strong> os números são uma simulação matemática para discussão de cenários, não uma previsão de resultado comercial.\n    </aside>\n  </main>\n\n  <script>\n    (() => {\n      const defaults = { leads: 400, conversion: 2 };\n      const leadsRange = document.getElementById('leadsRange');\n      const leadsNumber = document.getElementById('leadsNumber');\n      const conversionRange = document.getElementById('conversionRange');\n      const conversionNumber = document.getElementById('conversionNumber');\n      const salesResult = document.getElementById('salesResult');\n      const salesUnit = document.getElementById('salesUnit');\n      const formula = document.getElementById('formula');\n      const resetButton = document.getElementById('resetButton');\n\n      const clamp = (value, min, max) => Math.min(max, Math.max(min, value));\n      const formatInteger = new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 0 });\n      const formatDecimal = new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 1 });\n      const formatSales = new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 });\n\n      function readValues() {\n        const leads = clamp(Number(leadsNumber.value) || 0, 0, 1000);\n        const conversion = clamp(Number(String(conversionNumber.value).replace(',', '.')) || 0, 0, 10);\n        return { leads, conversion };\n      }\n\n      function update(animate = true) {\n        const { leads, conversion } = readValues();\n        const sales = leads * (conversion / 100);\n\n        leadsRange.value = leads;\n        conversionRange.value = conversion;\n        salesResult.textContent = formatSales.format(sales);\n        salesUnit.textContent = sales === 1 ? 'venda' : 'vendas';\n        formula.innerHTML = '<strong>Conta:</strong> ' +\n          formatInteger.format(leads) + ' leads × ' +\n          formatDecimal.format(conversion) + '% = ' +\n          formatSales.format(sales) + (sales === 1 ? ' venda' : ' vendas');\n\n        if (animate) {\n          salesResult.classList.remove('pulse');\n          requestAnimationFrame(() => {\n            salesResult.classList.add('pulse');\n            window.setTimeout(() => salesResult.classList.remove('pulse'), 140);\n          });\n        }\n      }\n\n      leadsRange.addEventListener('input', () => {\n        leadsNumber.value = leadsRange.value;\n        update();\n      });\n\n      conversionRange.addEventListener('input', () => {\n        conversionNumber.value = conversionRange.value;\n        update();\n      });\n\n      leadsNumber.addEventListener('input', update);", "\n      conversionNumber.addEventListener('input', update);\n\n      leadsNumber.addEventListener('change', () => {\n        leadsNumber.value = Math.round(clamp(Number(leadsNumber.value) || 0, 0, 1000));\n        update(false);\n      });\n\n      conversionNumber.addEventListener('change', () => {\n        conversionNumber.value = clamp(Number(String(conversionNumber.value).replace(',', '.')) || 0, 0, 10);\n        update(false);\n      });\n\n      resetButton.addEventListener('click', () => {\n        leadsNumber.value = defaults.leads;\n        leadsRange.value = defaults.leads;\n        conversionNumber.value = defaults.conversion;\n        conversionRange.value = defaults.conversion;\n        update();\n        leadsNumber.focus();\n      });\n\n      update(false);\n    })();\n  </script>\n</body>\n</html>\n"].join("")
};

export const GeneratedArtifact = ({example, source, language = "pt"}) => {
  const [artifact, setArtifact] = useState(null);
  const [expanded, setExpanded] = useState(false);
  const [canExpand, setCanExpand] = useState(false);
  const [expandFailed, setExpandFailed] = useState(false);
  const id = `generated-artifact-${example}`;
  const labels = language === "en" ? {
    loading: "Opening the generated example…",
    expand: "Enlarge example",
    restore: "Exit full screen",
    download: "Download original file",
    title: "Complete generated example",
    expandError: "Full screen is unavailable in this browser. You can still explore the example below or download it.",
    note: "Complete file generated in a Morada OS test conversation with fictional data. Scroll inside the example to explore the whole page."
  } : {
    loading: "Abrindo o exemplo gerado…",
    expand: "Ampliar exemplo",
    restore: "Sair da tela cheia",
    download: "Baixar arquivo original",
    title: "Exemplo completo gerado",
    expandError: "Não foi possível ampliar neste navegador. Você pode explorar o exemplo abaixo ou baixar o arquivo.",
    note: "Arquivo completo gerado em uma conversa de teste do Morada OS, com dados fictícios. Role dentro do exemplo para explorar a página inteira."
  };
  useEffect(() => {
    setExpandFailed(false);
    const escapes = {
      "&": "&amp;",
      '"': "&quot;",
      "<": "&lt;",
      ">": "&gt;"
    };
    const html = source.html.replace(/[&"<>]/g, character => escapes[character]);
    const policy = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; connect-src 'none'; base-uri 'none'; form-action 'none'; frame-src 'none'; worker-src 'none'; webrtc 'block'";
    const srcDoc = `<!doctype html><html><head><meta charset="utf-8"><base href="about:srcdoc"><meta http-equiv="Content-Security-Policy" content="${policy}"><meta name="referrer" content="no-referrer"><style>html,body{height:100%;margin:0}iframe{display:block;width:100%;height:100%;border:0}</style></head><body><iframe title="${language === "en" ? "Generated content" : "Conteúdo gerado"}" sandbox="allow-scripts allow-forms" referrerpolicy="no-referrer" srcdoc="${html}"></iframe></body></html>`;
    setArtifact({
      filename: source.filename,
      html: source.html,
      srcDoc
    });
  }, [source.html, source.filename, language]);
  useEffect(() => {
    setCanExpand(Boolean(document.fullscreenEnabled));
    const changed = () => setExpanded(document.fullscreenElement?.id === id);
    document.addEventListener("fullscreenchange", changed);
    return () => document.removeEventListener("fullscreenchange", changed);
  }, [id]);
  const download = () => {
    if (!artifact) return;
    const url = URL.createObjectURL(new Blob([artifact.html], {
      type: "text/html;charset=utf-8"
    }));
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = artifact.filename;
    document.body.appendChild(anchor);
    anchor.click();
    anchor.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  const enlarge = () => {
    setExpandFailed(false);
    const action = expanded ? document.exitFullscreen() : document.getElementById(id)?.requestFullscreen();
    action?.catch(() => setExpandFailed(true));
  };
  const css = `
.mo-artifact { --artifact-bg:#f8fafc; --artifact-ink:#1e293b; --artifact-line:#cbd5e1; --artifact-hover:#e2e8f0; margin:24px 0; border:1px solid var(--artifact-line); border-radius:16px; overflow:hidden; background:var(--artifact-bg); color:var(--artifact-ink); }
.dark .mo-artifact { --artifact-bg:#18212e; --artifact-ink:#f1f5f9; --artifact-line:#475569; --artifact-hover:#334155; }
.mo-artifact:fullscreen { width:100vw; height:100dvh; border:0; border-radius:0; margin:0; display:flex; flex-direction:column; }
.mo-artifact .artifact-toolbar { display:flex; align-items:center; flex-wrap:wrap; gap:8px; padding:12px; border-bottom:1px solid var(--artifact-line); flex-shrink:0; }
.mo-artifact .artifact-name { flex:1 1 180px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }
.mo-artifact button { min-height:44px; padding:8px 12px; border:1px solid var(--artifact-line); border-radius:8px; color:var(--artifact-ink); background:transparent; cursor:pointer; font:inherit; font-size:12px; }
.mo-artifact button:hover { background:var(--artifact-hover); }
.mo-artifact button:active { box-shadow:inset 0 0 0 2px var(--artifact-ink); }
.mo-artifact button:focus-visible { outline:3px solid #2683eb; outline-offset:2px; }
.mo-artifact button:disabled { cursor:default; opacity:.5; }
.mo-artifact > iframe { display:block; width:100%; height:720px; border:0; background:white; }
.mo-artifact:fullscreen > iframe { flex:1; min-height:0; height:100%; }
.mo-artifact .artifact-status { padding:24px; font-size:14px; }
.mo-artifact .artifact-note { margin:0; padding:12px; font-size:12px; line-height:1.6; color:var(--artifact-ink); border-top:1px solid var(--artifact-line); }
.mo-artifact:fullscreen .artifact-note { display:none; }
@media(max-width:600px) { .mo-artifact > iframe { height:640px; } .mo-artifact .artifact-name { flex-basis:100%; } }
`;
  return <section id={id} className="mo-artifact" aria-label={labels.title}>
      <style>{css}</style>
      <div className="artifact-toolbar">
        <span className="artifact-name" title={artifact?.filename}>
          {artifact?.filename || labels.title}
        </span>
        {canExpand && <button type="button" onClick={enlarge} disabled={!artifact}>
            {expanded ? labels.restore : labels.expand}
          </button>}
        <button type="button" onClick={download} disabled={!artifact}>
          {labels.download}
        </button>
      </div>
      {artifact ? <iframe title={labels.title} srcDoc={artifact.srcDoc} sandbox="allow-scripts allow-forms" referrerPolicy="no-referrer" /> : <div className="artifact-status" role="status">
          {labels.loading}
        </div>}
      {expandFailed && <p className="artifact-note" role="status">
          {labels.expandError}
        </p>}
      <p className="artifact-note">{labels.note}</p>
    </section>;
};

**You need:** a question and the calculation rules · **Example:** estimating sales from leads and conversion

In a sales meeting, someone asks: "What if we get more leads? What if conversion improves?" Instead of repeating the calculation for every question, ask for a **simulator**: a small tool where you change values and explore the results. It is an example of a miniapp you can create by talking to the assistant.

## Try the idea

Move the two controls. With 400 leads and 2% conversion, the estimate is 8 sales. This is an illustrative calculation, not a forecast based on your history.

<GeneratedArtifact example="miniapp" source={miniappArtifact} language="en" />

<Note>This complete file was generated by Morada OS from the Portuguese version of the prompt below; its content remains in Portuguese. Your result may vary. Continue the conversation to adjust what you need.</Note>

## Copy this prompt into chat

```text wrap theme={null}
Create a simulator so I can explore scenarios in a sales meeting. I want to change the monthly number of leads from 0 to 1,000 and the conversion rate from 0% to 10%. Start with 400 leads and 2%. Show estimated sales and explain the calculation: leads × conversion. Label the numbers as a simulation, not a forecast. Make it easy to use on a phone, include a reset button, and deliver it as a file I can open and use in this conversation.
```

**Why this prompt works:** it states the task, starting values, control limits, and expected result. This gives the assistant a concrete rule to turn into a tool.

## Open and use it in the conversation

1. Wait for the **.html** file to appear and **click its card**.
2. In the **Visualização** (Preview) tab, change the inputs and check that the result updates.
3. Use **Expandir visualização** (Expand preview) to show the miniapp during the meeting. Use **Baixar arquivo** (Download file) to keep the original.

See [the steps for using the controls and what gets saved](/morada-os/en/use-cases/create-visual-experiences#from-request-to-result-how-to-open-it).

## Keep building with the assistant

> "Add the sales target and show how far we are from reaching it."

> "I want to compare a conservative scenario and an optimistic one side by side."

> "Generate a new version starting with 600 leads and 3% conversion."

Use the same idea for area calculators, budget comparisons, or viewing checklists. Explain which rules apply and test a result you already know. This miniapp uses the data you provide; it does not query the CRM or automatically save changes made in the fields.
