Implemente um componente para convidar uma pessoa para uma equipe.

1. Intenção
A pessoa responsável pela equipe precisa convidar alguém com o perfil correto e saber se a tentativa deu certo.

2. Contexto
A pessoa já está na página de gestão da equipe. O convite é um bloco dessa página, alinhado à esquerda, sem virar uma tela promocional independente. Será usado em computador, celular e por teclado.

3. Decisões já tomadas
E-mail obrigatório. Únicos perfis: Leitor (valor 'leitor', padrão) e Editor (valor 'editor'). Hierarquia: 'Gestão de pessoas' como contexto da página; 'Convidar uma pessoa' como título do bloco; ajuda curta, campos, ação e retorno. Conteúdo até 640px. A partir de 720px de viewport, e-mail e perfil ficam na mesma linha, proporção 2:1; abaixo, empilhar. Ação abaixo à esquerda. Reutilize os tokens fornecidos, sem sombra decorativa, hero, sidebar ou avatar inventado.

4. Critérios de aceite
C10. Respeitar a composição e os tokens descritos acima.
C3. Durante a espera, informar andamento e impedir chamadas duplicadas, inclusive por Enter e cliques rápidos.
C6. Em falha@example.com, preservar campos e permitir nova tentativa; primeira chamada falha e segunda funciona.
C1. Vazio/inválido não chama API; erro junto ao e-mail, valor preservado e foco no campo.
C2. Leitor vem selecionado; só Leitor e Editor.
C4. Sucesso mostra e-mail e perfil efetivamente enviados.
C5. existe@example.com explica pessoa já cadastrada, preserva campos e permite corrigir.
C7. Tab, Shift+Tab e Enter operam o formulário, com foco visível. Não puxar foco de quem saiu do formulário durante uma espera assíncrona.
C8. Labels associados; erro relacionado ao e-mail; aria-invalid quando houver erro; região de status presente desde o início para andamento/sucesso.
C9. Em 320px e zoom de 200%, não cortar conteúdo nem gerar rolagem horizontal no formulário.
D1 escolhida para este exemplo. Depois de sucesso, ao editar e-mail ou perfil, limpar a confirmação anterior. Teste: enviar novo@example.com com Leitor, aguardar sucesso, alterar para Editor e conferir que a confirmação anterior desaparece.

5. Fora de escopo
Backend real, e-mail real, autenticação, autorização de servidor, persistência e redesenho de outras partes do produto. O perfil do exercício não implementa segurança de servidor.

Use a base HTML abaixo. Preserve literalmente os tokens, o bloco API SIMULADA e o painel do laboratório, incluindo IDs, cenários, latência e registros de chamadas. Use window.apiConvite({ email, perfil }) para a operação, sem rede ou envio real. Entregue um arquivo HTML completo, com CSS e JavaScript no próprio arquivo, sem dependências, fontes remotas ou instalação. Interface e mensagens em português brasileiro.

BASE HTML INTEGRAL:

<!doctype html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Base inicial | Convite para equipe</title>
  <style>
    :root {
      --ink: #161722;
      --muted: #54535b;
      --paper: #e8e4dc;
      --surface: #ffffff;
      --accent: #dc000c;
      --line: #bbb8b1;
      --radius: 8px;
      --space: 8px;
      --error: #b4000a;
      color-scheme: light;
      font-family: system-ui, sans-serif;
      color: var(--ink);
      background: var(--surface);
    }
    * { box-sizing: border-box; }
    body { margin: 0; padding: 32px 20px; }
    main { max-width: 640px; margin: 0; }
    h1 { margin: 0 0 16px; font-size: clamp(1.7rem, 5vw, 2.1rem); line-height: 1.2; }
    p { line-height: 1.6; }
    a { color: var(--ink); }
    :focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; }
    .lab-info { color: var(--muted); font-size: .95rem; }
    .lab-info strong { color: var(--ink); }
    #componente { margin: 32px 0; padding: 24px; border: 1px dashed var(--line); border-radius: var(--radius); }
    code { overflow-wrap: anywhere; }
    .laboratorio { max-width: 640px; margin-top: 32px; border-top: 1px solid var(--line); padding-top: 16px; color: var(--muted); font-size: .9rem; }
    .laboratorio summary { color: var(--ink); cursor: pointer; padding: 8px 0; }
    .laboratorio p, .laboratorio li { line-height: 1.6; }
    .laboratorio ul { padding-left: 24px; }
    .laboratorio dl { margin: 16px 0; }
    .laboratorio dl div { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--line); }
    .laboratorio dt { font-weight: 600; }
    .laboratorio dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
    code { overflow-wrap: anywhere; }

  </style>
</head>
<body>
  <main>
    <h1>Base inicial do exercício</h1>
    <p class="lab-info">Este arquivo contém os tokens visuais e uma API simulada. <strong>O formulário ainda não foi implementado.</strong> Use esta mesma base nas duas gerações.</p>
    <p class="lab-info">Na aula, abra primeiro as saídas A/B salvas. Para gerar uma nova versão, use o arquivo original e preserve a API, o painel e os tokens. Salve a resposta em outro arquivo.</p>

    <!-- Substitua este bloco pelo componente. Preserve os tokens e a API simulada. -->
    <div id="componente">
      <p>Implemente aqui o convite de uma pessoa para a equipe.</p>
    </div>

  </main>
    <!-- PAINEL DO LABORATÓRIO: preserve este bloco fora da interface do produto. -->
    <aside class="laboratorio" aria-label="Instrumentação do exercício">
      <details id="painel-laboratorio">
        <summary>Painel do laboratório</summary>
        <p>Registro da API simulada. Este painel não faz parte do produto e não avalia a interface.</p>
        <dl>
          <div><dt>Chamadas</dt><dd><output id="chamadas">0</output></dd></div>
          <div><dt>Último e-mail</dt><dd><output id="lab-email">Nenhum</output></dd></div>
          <div><dt>Último perfil</dt><dd><output id="lab-perfil">Nenhum</output></dd></div>
          <div><dt>Estado da última chamada</dt><dd><output id="lab-estado">Sem chamada</output></dd></div>
        </dl>
        <ul>
          <li><code>novo@example.com</code>: sucesso após 1,2 segundo.</li>
          <li><code>existe@example.com</code>: pessoa já faz parte da equipe.</li>
          <li><code>falha@example.com</code>: primeira tentativa falha; segunda funciona.</li>
        </ul>
        <p>Recarregar reinicia contagem, registro e tentativas. Nenhum convite real é enviado. A simulação não implementa servidor, autenticação ou autorização.</p>
      </details>
    </aside>
  <script>
    // API SIMULADA: preserve este bloco nas duas gerações.
    // Não há rede, persistência, autorização de servidor ou envio real.
    (() => {
      const tentativas = new Map();
      window.chamadasConvite = 0;
      window.registroConvites = [];

      function atualizarPainel() {
        const ultimo = window.registroConvites[window.registroConvites.length - 1];
        const valores = {
          chamadas: window.chamadasConvite,
          'lab-email': ultimo ? ultimo.email : 'Nenhum',
          'lab-perfil': ultimo ? ultimo.perfil : 'Nenhum',
          'lab-estado': ultimo ? ultimo.estado : 'Sem chamada'
        };
        for (const [id, valor] of Object.entries(valores)) {
          const campo = document.getElementById(id);
          if (campo) campo.textContent = String(valor);
        }
      }

      window.apiConvite = async ({ email, perfil }) => {
        window.chamadasConvite += 1;
        const registro = { id: window.chamadasConvite, email, perfil, estado: 'pendente' };
        window.registroConvites.push(registro);
        atualizarPainel();

        const chave = String(email).trim().toLowerCase();
        const tentativa = (tentativas.get(chave) || 0) + 1;
        tentativas.set(chave, tentativa);
        await new Promise((resolve) => setTimeout(resolve, 1200));

        if (chave === 'existe@example.com') {
          registro.estado = 'existente';
          atualizarPainel();
          return { status: 'existente', email, perfil };
        }
        if (chave === 'falha@example.com' && tentativa === 1) {
          registro.estado = 'falha';
          atualizarPainel();
          throw new Error('Falha temporária simulada.');
        }
        registro.estado = 'enviado';
        atualizarPainel();
        return { status: 'enviado', email, perfil };
      };
    })();
  </script>
  <script>
    // Implemente aqui o comportamento do componente.
  </script>
</body>
</html>
