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

# Build a report you can explore

> Turn numbers into a page with a summary, comparisons, and filters to guide a conversation with the data in view.

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>;
};

**You need:** data in your prompt or a reference file · **Example:** monthly sales by region

While you are presenting results, someone asks: "Show just February." In an interactive report, you change the filter and continue the conversation. The page brings the summary and data together, so the reader does not have to browse several messages to find a comparison.

## Change the period and see the difference

This example uses only the fictional numbers in the prompt below. January totals 20 sales; February totals 25. The filter changes the total and the comparison by region.

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

<Note>This complete report was generated by Morada OS from the Portuguese version of the prompt below; its content remains in Portuguese. It uses fictional data with no CRM connection. Another request may produce a different layout.</Note>

## Start with this complete prompt

```text wrap theme={null}
Build an interactive report for a sales meeting. Use only these fictional data: January — North 8 sales, South 5, Central 7; February — North 10, South 6, Central 9. I want a short summary, total sales, a chart comparing the regions, a data table, and a period filter (January, February, or both). The filter must update the numbers and chart together. Show the period and unit, label the data as fictional, and do not invent reasons for the differences. Make it readable on a phone and deliver it as a file I can open and explore in chat.
```

To use your own data, attach the spreadsheet or report and replace the example section with "use the data from the file I sent." Specify which sheet, period, or subset to use. You do not need to connect a system to try this recipe.

## Open the file and check it

**Click the .html card** and use **Visualização** (Preview). Change the filters and verify one total against the original source before the meeting. **Expandir visualização** (Expand preview) gives you more room to present; **Baixar arquivo** (Download file) saves the original report.

When the data changes, send the new dataset and ask for another version: this report does not fetch updates automatically.

## Shape the report around the meeting

> "Open with the main difference between the months and put the detailed table after the chart."

> "Add a comparison between periods, showing the calculation used."

> "Generate a shorter version for leadership, keeping the period, source, and assumptions."

The visual helps you compare; the conclusions still need to be supported by the data. An increase in sales alone does not explain why it happened. For a document without interaction, you may prefer [a summary for leadership](/morada-os/en/use-cases/summarize-a-report).

[See how to download the file and what gets saved](/morada-os/en/use-cases/create-visual-experiences#what-to-expect-from-this-experience).
