> ## 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.

# Monte um relatório que você pode explorar

> Transforme números em uma página com resumo, comparação e filtros para conduzir uma conversa com os dados à vista.

export const reportArtifact = {
  filename: "relatorio-interativo-vendas-ficticias.html",
  sha256: "12ba0d79c042e825a74f8eb35a4662be53e23130e1c153f4870f91413a6c163f",
  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.0\">\n  <title>Relatório interativo de vendas — dados fictícios</title>\n  <style>\n    :root {\n      --bg: #f4f7f6;\n      --surface: #ffffff;\n      --surface-soft: #eef5f2;\n      --text: #17352f;\n      --muted: #5f746f;\n      --line: #d9e4e0;\n      --accent: #13795b;\n      --accent-dark: #0d5b44;\n      --accent-soft: #dcefe8;\n      --north: #13795b;\n      --center: #2f78a8;\n      --south: #d88728;\n      --shadow: 0 12px 30px rgba(20, 55, 47, 0.08);\n      --radius-lg: 22px;\n      --radius-md: 14px;\n      --radius-sm: 10px;\n    }\n\n    * { box-sizing: border-box; }\n\n    body {\n      margin: 0;\n      min-width: 280px;\n      background: var(--bg);\n      color: var(--text);\n      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      line-height: 1.5;\n    }\n\n    button, input { font: inherit; }\n\n    .page {\n      width: min(1120px, calc(100% - 28px));\n      margin: 0 auto;\n      padding: 28px 0 44px;\n    }\n\n    .hero {\n      display: grid;\n      grid-template-columns: minmax(0, 1fr) auto;\n      gap: 24px;\n      align-items: end;\n      margin-bottom: 18px;\n    }\n\n    .eyebrow {\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      margin: 0 0 8px;\n      color: var(--accent-dark);\n      font-size: 0.78rem;\n      font-weight: 800;\n      letter-spacing: 0.08em;\n      text-transform: uppercase;\n    }\n\n    .eyebrow::before {\n      content: \"\";\n      width: 9px;\n      height: 9px;\n      border-radius: 50%;\n      background: var(--accent);\n      box-shadow: 0 0 0 5px var(--accent-soft);\n    }\n\n    h1 {\n      max-width: 760px;\n      margin: 0;\n      font-size: clamp(1.75rem, 4vw, 3rem);\n      line-height: 1.08;\n      letter-spacing: -0.04em;\n    }\n\n    .subtitle {\n      max-width: 720px;\n      margin: 12px 0 0;\n      color: var(--muted);\n      font-size: 1rem;\n    }\n\n    .fictional-badge {\n      align-self: start;\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      padding: 9px 12px;\n      border: 1px solid #efc98f;\n      border-radius: 999px;\n      background: #fff7ea;\n      color: #805218;\n      font-size: 0.82rem;\n      font-weight: 800;\n      white-space: nowrap;\n    }\n\n    .filter-panel {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 20px;\n      margin: 22px 0;\n      padding: 16px;\n      border: 1px solid var(--line);\n      border-radius: var(--radius-lg);\n      background: var(--surface);\n      box-shadow: var(--shadow);\n    }\n\n    .filter-copy strong,\n    .filter-copy span { display: block; }\n    .filter-copy strong { font-size: 0.98rem; }\n    .filter-copy span { color: var(--muted); font-size: 0.84rem; }\n\n    .segmented {\n      display: grid;\n      grid-template-columns: repeat(3, 1fr);\n      gap: 6px;\n      min-width: 390px;\n      padding: 5px;\n      border-radius: 14px;\n      background: var(--surface-soft);\n    }\n\n    .period-button {\n      min-height: 44px;\n      padding: 9px 15px;\n      border: 0;\n      border-radius: 10px;\n      background: transparent;\n      color: var(--muted);\n      cursor: pointer;\n      font-weight: 750;\n      transition: background 160ms ease, color 160ms ease, box-shadow 160ms ease;\n    }\n\n    .period-button:hover { color: var(--text); }\n    .period-button[aria-pressed=\"true\"] {\n      background: var(--surface);\n      color: var(--accent-dark);\n      box-shadow: 0 2px 10px rgba(20, 55, 47, 0.10);\n    }\n\n    .period-button:focus-visible {\n      outline: 3px solid rgba(19, 121, 91, 0.28);\n      outline-offset: 2px;\n    }\n\n    .kpi-grid {\n      display: grid;\n      grid-template-columns: 1.15fr 1fr 1fr;\n      gap: 14px;\n      margin-bottom: 14px;\n    }\n\n    .card {\n      border: 1px solid var(--line);\n      border-radius: var(--radius-lg);\n      background: var(--surface);\n      box-shadow: var(--shadow);\n    }\n\n    .kpi {\n", "      min-height: 142px;\n      padding: 20px;\n    }\n\n    .kpi.primary {\n      border-color: transparent;\n      background: var(--text);\n      color: #fff;\n    }\n\n    .kpi-label {\n      margin: 0 0 10px;\n      color: var(--muted);\n      font-size: 0.76rem;\n      font-weight: 800;\n      letter-spacing: 0.07em;\n      text-transform: uppercase;\n    }\n\n    .primary .kpi-label { color: #bcd0ca; }\n\n    .kpi-value {\n      margin: 0;\n      font-size: clamp(1.45rem, 4vw, 2.45rem);\n      font-weight: 850;\n      line-height: 1.05;\n      letter-spacing: -0.04em;\n    }\n\n    .kpi-unit {\n      margin: 9px 0 0;\n      color: var(--muted);\n      font-size: 0.88rem;\n    }\n\n    .primary .kpi-unit { color: #d6e3df; }\n\n    .summary-card {\n      display: grid;\n      grid-template-columns: auto 1fr;\n      gap: 14px;\n      align-items: start;\n      margin-bottom: 14px;\n      padding: 18px 20px;\n    }\n\n    .summary-icon {\n      display: grid;\n      place-items: center;\n      width: 38px;\n      height: 38px;\n      border-radius: 12px;\n      background: var(--accent-soft);\n      color: var(--accent-dark);\n      font-weight: 900;\n    }\n\n    .summary-card h2 {\n      margin: 0 0 4px;\n      font-size: 0.8rem;\n      letter-spacing: 0.06em;\n      text-transform: uppercase;\n    }\n\n    .summary-card p { margin: 0; color: var(--muted); }\n\n    .content-grid {\n      display: grid;\n      grid-template-columns: minmax(0, 1.45fr) minmax(330px, 0.85fr);\n      gap: 14px;\n      align-items: start;\n    }\n\n    .section-card { padding: 22px; }\n\n    .section-head {\n      display: flex;\n      align-items: flex-start;\n      justify-content: space-between;\n      gap: 16px;\n      margin-bottom: 20px;\n    }\n\n    .section-head h2 {\n      margin: 0;\n      font-size: 1.15rem;\n      letter-spacing: -0.02em;\n    }\n\n    .section-head p {\n      margin: 4px 0 0;\n      color: var(--muted);\n      font-size: 0.86rem;\n    }\n\n    .chart-period {\n      flex: none;\n      padding: 6px 9px;\n      border-radius: 999px;\n      background: var(--surface-soft);\n      color: var(--accent-dark);\n      font-size: 0.75rem;\n      font-weight: 800;\n    }\n\n    .chart {\n      display: grid;\n      gap: 18px;\n      min-height: 254px;\n    }\n\n    .bar-row {\n      display: grid;\n      grid-template-columns: 72px minmax(0, 1fr) 48px;\n      gap: 12px;\n      align-items: center;\n    }\n\n    .bar-label { font-size: 0.9rem; font-weight: 800; }\n\n    .bar-track {\n      position: relative;\n      height: 42px;\n      overflow: hidden;\n      border-radius: 10px;\n      background:\n        linear-gradient(to right, transparent 24.6%, var(--line) 25%, transparent 25.4%),\n        linear-gradient(to right, transparent 49.6%, var(--line) 50%, transparent 50.4%),\n        linear-gradient(to right, transparent 74.6%, var(--line) 75%, transparent 75.4%),\n        #f7faf9;\n    }\n\n    .bar-fill {\n      width: 0;\n      height: 100%;\n      min-width: 2px;\n      border-radius: 10px;\n      transform-origin: left;\n      transition: width 260ms ease;\n    }\n\n    .bar-fill[data-region=\"Norte\"] { background: var(--north); }\n    .bar-fill[data-region=\"Centro\"] { background: var(--center); }\n    .bar-fill[data-region=\"Sul\"] { background: var(--south); }\n\n    .bar-value {\n      color: var(--text);\n      font-size: 0.94rem;\n      font-weight: 850;\n      text-align: right;\n    }\n\n    .axis {\n      display: grid;\n      grid-template-columns: 72px minmax(0, 1fr) 48px;\n      gap: 12px;\n      margin-top: -7px;\n      color: var(--muted);\n      font-size: 0.7rem;\n    }\n\n    .axis-values {\n      display: flex;\n      justify-content: space-between;\n    }\n\n    .unit-note {\n      margin: 18px 0 0;\n      padding-top: 14px;\n      border-top: 1px solid var(--line);\n      color: var(--muted);\n      font-size: 0.8rem;\n    }\n\n    .table-wrap {\n      overflow-x: auto;\n      border: 1px solid var(--line);\n      border-radius: var(--radius-md);\n    }\n\n    table {\n      width: 100%;\n      border-collapse: collapse;\n      min-width: 330px;\n      font-size: 0.88rem;\n    }\n\n ", "   th, td {\n      padding: 12px 13px;\n      border-bottom: 1px solid var(--line);\n      text-align: left;\n      white-space: nowrap;\n    }\n\n    th {\n      background: #f7faf9;\n      color: var(--muted);\n      font-size: 0.72rem;\n      font-weight: 850;\n      letter-spacing: 0.05em;\n      text-transform: uppercase;\n    }\n\n    td:last-child, th:last-child { text-align: right; }\n    tbody tr:last-child td { border-bottom: 0; }\n\n    tfoot td {\n      border-top: 2px solid var(--text);\n      border-bottom: 0;\n      background: var(--surface-soft);\n      font-weight: 850;\n    }\n\n    .region-dot {\n      display: inline-block;\n      width: 8px;\n      height: 8px;\n      margin-right: 7px;\n      border-radius: 50%;\n      vertical-align: 1px;\n    }\n\n    .region-dot.norte { background: var(--north); }\n    .region-dot.centro { background: var(--center); }\n    .region-dot.sul { background: var(--south); }\n\n    footer {\n      margin-top: 16px;\n      color: var(--muted);\n      font-size: 0.76rem;\n      text-align: center;\n    }\n\n    @media (max-width: 820px) {\n      .hero { grid-template-columns: 1fr; gap: 14px; }\n      .fictional-badge { justify-self: start; }\n      .filter-panel { align-items: stretch; flex-direction: column; }\n      .segmented { min-width: 0; width: 100%; }\n      .content-grid { grid-template-columns: 1fr; }\n    }\n\n    @media (max-width: 590px) {\n      .page { width: min(100% - 20px, 1120px); padding-top: 18px; }\n      .kpi-grid { grid-template-columns: 1fr 1fr; }\n      .kpi.primary { grid-column: 1 / -1; }\n      .kpi { min-height: 118px; padding: 17px; }\n      .section-card { padding: 17px; }\n      .section-head { flex-direction: column; margin-bottom: 18px; }\n      .bar-row { grid-template-columns: 58px minmax(0, 1fr) 40px; gap: 8px; }\n      .axis { grid-template-columns: 58px minmax(0, 1fr) 40px; gap: 8px; }\n      .segmented { grid-template-columns: 1fr; }\n      .period-button { width: 100%; }\n    }\n\n    @media (prefers-reduced-motion: reduce) {\n      * { scroll-behavior: auto !important; }\n      .bar-fill, .period-button { transition: none; }\n    }\n  </style>\n</head>\n<body>\n  <main class=\"page\">\n    <header class=\"hero\">\n      <div>\n        <p class=\"eyebrow\">Reunião de vendas</p>\n        <h1>Desempenho comercial por região</h1>\n        <p class=\"subtitle\">Visão comparativa de janeiro e fevereiro, com atualização conjunta dos indicadores, gráfico e tabela.</p>\n      </div>\n      <div class=\"fictional-badge\" aria-label=\"Aviso: os dados deste relatório são fictícios\">Dados fictícios</div>\n    </header>\n\n    <section class=\"filter-panel\" aria-labelledby=\"filter-title\">\n      <div class=\"filter-copy\">\n        <strong id=\"filter-title\">Filtrar período</strong>\n        <span>Selecione uma opção para atualizar todo o relatório.</span>\n      </div>\n      <div class=\"segmented\" role=\"group\" aria-label=\"Período do relatório\">\n        <button class=\"period-button\" type=\"button\" data-period=\"janeiro\" aria-pressed=\"false\">Janeiro</button>\n        <button class=\"period-button\" type=\"button\" data-period=\"fevereiro\" aria-pressed=\"false\">Fevereiro</button>\n        <button class=\"period-button\" type=\"button\" data-period=\"ambos\" aria-pressed=\"true\">Os dois</button>\n      </div>\n    </section>\n\n    <section class=\"kpi-grid\" aria-label=\"Indicadores do período\">\n      <article class=\"card kpi primary\">\n        <p class=\"kpi-label\">Total de vendas</p>\n        <p class=\"kpi-value\" id=\"total-sales\" aria-live=\"polite\">45</p>\n        <p class=\"kpi-unit\">Unidade: vendas</p>\n      </article>\n      <article class=\"card kpi\">\n        <p class=\"kpi-label\">Período</p>\n        <p class=\"kpi-value\" id=\"period-label\">Janeiro + fevereiro</p>\n        <p class=\"kpi-unit\">Recorte selecionado</p>\n      </article>\n      <article class=\"card kpi\">\n        <p class=\"kpi-label\">Regiões</p>\n        <p class=\"kpi-value\">3</p>\n        <p class=\"kpi-unit\">Norte, Sul e Centro</p>\n      </article>\n    </section>\n\n    <section class=\"card summary-card\" aria-labelledby=\"s", "ummary-title\">\n      <div class=\"summary-icon\" aria-hidden=\"true\">↗</div>\n      <div>\n        <h2 id=\"summary-title\">Resumo</h2>\n        <p id=\"summary-text\" aria-live=\"polite\">Nos dois meses, foram 45 vendas: Norte somou 18, Centro 16 e Sul 11.</p>\n      </div>\n    </section>\n\n    <div class=\"content-grid\">\n      <section class=\"card section-card\" aria-labelledby=\"chart-title\">\n        <div class=\"section-head\">\n          <div>\n            <h2 id=\"chart-title\">Comparação entre regiões</h2>\n            <p>Total de vendas por região no período selecionado.</p>\n          </div>\n          <span class=\"chart-period\" id=\"chart-period\">Janeiro + fevereiro</span>\n        </div>\n\n        <div class=\"chart\" id=\"chart\" role=\"img\" aria-label=\"Gráfico de barras: Norte 18 vendas, Centro 16 vendas e Sul 11 vendas\">\n          <div class=\"bar-row\">\n            <span class=\"bar-label\">Norte</span>\n            <div class=\"bar-track\"><div class=\"bar-fill\" data-region=\"Norte\" style=\"width: 100%\"></div></div>\n            <span class=\"bar-value\" data-value-region=\"Norte\">18</span>\n          </div>\n          <div class=\"bar-row\">\n            <span class=\"bar-label\">Centro</span>\n            <div class=\"bar-track\"><div class=\"bar-fill\" data-region=\"Centro\" style=\"width: 88.89%\"></div></div>\n            <span class=\"bar-value\" data-value-region=\"Centro\">16</span>\n          </div>\n          <div class=\"bar-row\">\n            <span class=\"bar-label\">Sul</span>\n            <div class=\"bar-track\"><div class=\"bar-fill\" data-region=\"Sul\" style=\"width: 61.11%\"></div></div>\n            <span class=\"bar-value\" data-value-region=\"Sul\">11</span>\n          </div>\n        </div>\n        <div class=\"axis\" aria-hidden=\"true\">\n          <span></span>\n          <div class=\"axis-values\"><span>0</span><span id=\"axis-quarter\">5</span><span id=\"axis-half\">9</span><span id=\"axis-three-quarter\">14</span><span id=\"axis-max\">18</span></div>\n          <span></span>\n        </div>\n        <p class=\"unit-note\">Unidade do gráfico: vendas. Valores agregados quando “Os dois” está selecionado.</p>\n      </section>\n\n      <section class=\"card section-card\" aria-labelledby=\"table-title\">\n        <div class=\"section-head\">\n          <div>\n            <h2 id=\"table-title\">Dados do relatório</h2>\n            <p id=\"table-description\">Janeiro e fevereiro · unidade: vendas</p>\n          </div>\n        </div>\n        <div class=\"table-wrap\">\n          <table>\n            <thead>\n              <tr>\n                <th scope=\"col\">Período</th>\n                <th scope=\"col\">Região</th>\n                <th scope=\"col\">Vendas</th>\n              </tr>\n            </thead>\n            <tbody id=\"data-table-body\">\n              <tr><td>Janeiro</td><td><span class=\"region-dot norte\"></span>Norte</td><td>8</td></tr>\n              <tr><td>Janeiro</td><td><span class=\"region-dot sul\"></span>Sul</td><td>5</td></tr>\n              <tr><td>Janeiro</td><td><span class=\"region-dot centro\"></span>Centro</td><td>7</td></tr>\n              <tr><td>Fevereiro</td><td><span class=\"region-dot norte\"></span>Norte</td><td>10</td></tr>\n              <tr><td>Fevereiro</td><td><span class=\"region-dot sul\"></span>Sul</td><td>6</td></tr>\n              <tr><td>Fevereiro</td><td><span class=\"region-dot centro\"></span>Centro</td><td>9</td></tr>\n            </tbody>\n            <tfoot>\n              <tr><td colspan=\"2\">Total do período</td><td id=\"table-total\">45</td></tr>\n            </tfoot>\n          </table>\n        </div>\n      </section>\n    </div>\n\n    <footer>Relatório demonstrativo elaborado exclusivamente com dados fictícios fornecidos para esta reunião.</footer>\n  </main>\n\n  <script>\n    const salesData = [\n      { period: 'janeiro', periodLabel: 'Janeiro', region: 'Norte', sales: 8 },\n      { period: 'janeiro', periodLabel: 'Janeiro', region: 'Sul', sales: 5 },\n      { period: 'janeiro', periodLabel: 'Janeiro', region: 'Centro', sales: 7 },\n      { period: 'fevereiro', periodLabel: 'Fevereiro', region: 'Norte', sa", "les: 10 },\n      { period: 'fevereiro', periodLabel: 'Fevereiro', region: 'Sul', sales: 6 },\n      { period: 'fevereiro', periodLabel: 'Fevereiro', region: 'Centro', sales: 9 }\n    ];\n\n    const regionOrder = ['Norte', 'Centro', 'Sul'];\n    const regionClass = { Norte: 'norte', Centro: 'centro', Sul: 'sul' };\n    const periodNames = {\n      janeiro: 'Janeiro',\n      fevereiro: 'Fevereiro',\n      ambos: 'Janeiro + fevereiro'\n    };\n\n    const buttons = [...document.querySelectorAll('.period-button')];\n    const totalSales = document.getElementById('total-sales');\n    const periodLabel = document.getElementById('period-label');\n    const summaryText = document.getElementById('summary-text');\n    const chartPeriod = document.getElementById('chart-period');\n    const chart = document.getElementById('chart');\n    const tableBody = document.getElementById('data-table-body');\n    const tableTotal = document.getElementById('table-total');\n    const tableDescription = document.getElementById('table-description');\n\n    function getRows(period) {\n      return period === 'ambos' ? salesData : salesData.filter(item => item.period === period);\n    }\n\n    function aggregateByRegion(rows) {\n      return regionOrder.map(region => ({\n        region,\n        sales: rows.filter(item => item.region === region).reduce((sum, item) => sum + item.sales, 0)\n      }));\n    }\n\n    function makeCell(text) {\n      const td = document.createElement('td');\n      td.textContent = text;\n      return td;\n    }\n\n    function renderTable(rows, total) {\n      tableBody.textContent = '';\n      rows.forEach(item => {\n        const tr = document.createElement('tr');\n        tr.appendChild(makeCell(item.periodLabel));\n\n        const regionCell = document.createElement('td');\n        const dot = document.createElement('span');\n        dot.className = `region-dot ${regionClass[item.region]}`;\n        dot.setAttribute('aria-hidden', 'true');\n        regionCell.appendChild(dot);\n        regionCell.appendChild(document.createTextNode(item.region));\n        tr.appendChild(regionCell);\n\n        tr.appendChild(makeCell(String(item.sales)));\n        tableBody.appendChild(tr);\n      });\n      tableTotal.textContent = String(total);\n    }\n\n    function renderChart(regional) {\n      const maxValue = Math.max(...regional.map(item => item.sales), 1);\n\n      regional.forEach(item => {\n        const fill = document.querySelector(`.bar-fill[data-region=\"${item.region}\"]`);\n        const value = document.querySelector(`[data-value-region=\"${item.region}\"]`);\n        fill.style.width = `${(item.sales / maxValue) * 100}%`;\n        value.textContent = String(item.sales);\n      });\n\n      document.getElementById('axis-quarter').textContent = String(Math.round(maxValue * 0.25));\n      document.getElementById('axis-half').textContent = String(Math.round(maxValue * 0.5));\n      document.getElementById('axis-three-quarter').textContent = String(Math.round(maxValue * 0.75));\n      document.getElementById('axis-max').textContent = String(maxValue);\n\n      chart.setAttribute(\n        'aria-label',\n        `Gráfico de barras: ${regional.map(item => `${item.region} ${item.sales} vendas`).join(', ')}`\n      );\n    }\n\n    function updateReport(period) {\n      const rows = getRows(period);\n      const total = rows.reduce((sum, item) => sum + item.sales, 0);\n      const regional = aggregateByRegion(rows);\n      const periodName = periodNames[period];\n      const regionalText = regional.map(item => `${item.region} ${item.sales}`).join(', ');\n\n      buttons.forEach(button => {\n        button.setAttribute('aria-pressed', String(button.dataset.period === period));\n      });\n\n      totalSales.textContent = String(total);\n      periodLabel.textContent = periodName;\n      chartPeriod.textContent = periodName;\n      tableDescription.textContent = `${periodName} · unidade: vendas`;\n\n      if (period === 'ambos') {\n        summaryText.textContent = `Nos dois meses, foram ${total} vendas: ${regionalText}.`;\n     ", " } else {\n        summaryText.textContent = `Em ${periodName.toLowerCase()}, foram ${total} vendas: ${regionalText}.`;\n      }\n\n      renderChart(regional);\n      renderTable(rows, total);\n    }\n\n    buttons.forEach(button => {\n      button.addEventListener('click', () => updateReport(button.dataset.period));\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>;
};

**Precisa de:** dados no pedido ou um arquivo de referência · **Exemplo:** vendas mensais por região

Durante a apresentação do resultado, alguém pede: "Mostre só fevereiro". Num relatório interativo, você troca o filtro e continua a conversa. A página reúne o resumo e os dados, sem obrigar o leitor a percorrer várias mensagens para encontrar uma comparação.

## Mude o período e veja a diferença

Este exemplo usa apenas os números fictícios do pedido abaixo. O total de janeiro é 20 vendas; o de fevereiro é 25. O filtro altera o total e a comparação por região.

<GeneratedArtifact example="report" source={reportArtifact} language="pt" />

<Note>Relatório completo gerado no chat a partir dos dados fictícios abaixo, sem ligação com o CRM. Outro pedido pode produzir outro layout.</Note>

## Comece com este pedido completo

```text wrap theme={null}
Monte um relatório interativo para uma reunião de vendas. Use somente estes dados fictícios: janeiro — Norte 8 vendas, Sul 5, Centro 7; fevereiro — Norte 10, Sul 6, Centro 9. Quero um resumo curto, o total de vendas, um gráfico comparando as regiões, a tabela dos dados e um filtro de período (janeiro, fevereiro ou os dois). O filtro deve atualizar os números e o gráfico juntos. Mostre período e unidade, identifique os dados como fictícios e não invente causas para as diferenças. Deixe legível no celular e entregue como um arquivo que eu possa abrir e explorar no chat.
```

Para usar seus dados, anexe a planilha ou o relatório e substitua o trecho de exemplo por "use os dados do arquivo que enviei". Diga qual aba, período ou recorte vale. Não é preciso conectar um sistema para experimentar esta receita.

## Abra o arquivo e confira

**Clique no card do .html** e use **Visualização**. Troque os filtros e confirme um total com a fonte original antes da reunião. **Expandir visualização** dá mais espaço para a apresentação; **Baixar arquivo** guarda o relatório original.

Quando os dados mudarem, envie a nova base e peça outra versão: esse relatório não busca atualizações automaticamente.

## Faça o relatório responder à reunião

> "Abra com a principal diferença entre os meses e deixe a tabela de detalhes depois do gráfico."

> "Acrescente uma comparação entre os períodos, mostrando a conta usada."

> "Gere uma versão mais enxuta para a diretoria, mantendo o período, a fonte e as premissas."

O visual ajuda a comparar; as conclusões precisam continuar sustentadas pelos dados. Um aumento de vendas, sozinho, não explica o motivo do aumento. Para um documento sem interação, você pode preferir [um resumo para a diretoria](/morada-os/casos-de-uso/resuma-um-relatorio).

[Veja como baixar e o que fica salvo no arquivo](/morada-os/casos-de-uso/crie-experiencias-visuais#o-que-esperar-dessa-experiencia).
