← Biblioteca componentes/listas/agenda-eventos/ficha.md CódigoTela cheia
listas/agenda-eventos

Agenda de eventos que expira sozinha

🟢 estavel complexidade média prefixo agn- agenda eventos evento data inscricao webinar institucional

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

<link rel="stylesheet" href="componentes/listas/agenda-eventos/estilo.css">
<script src="componentes/listas/agenda-eventos/script.js"></script>

markup.html · 117 linhas · 5,4 KB

<!-- ==========================================================================
     AGENDA DE EVENTOS — a data grande, e o convite que expira sozinho
     Prefixo: agn-
     Requer: core (tokens, base, motion.css, motion.js) + este estilo.css e
             este script.js

     Uma lista de encontros em que a data é a primeira coisa que o olho lê: o
     dia em corpo de display, o mês e o dia da semana embaixo. Ao lado, o
     formato (presencial, online), o título, uma linha e o horário. No fim da
     linha, o botão de inscrição.

     O CONVITE EXPIRA SOZINHO. Cada evento declara início e fim com fuso
     (data-agn-inicio / data-agn-fim), e o script compara com o relógio de
     quem está olhando:
       antes do início   nada muda — o convite vale
       durante           selo "Acontecendo agora"
       depois do fim     o BOTÃO SAI DO DOM (texto e href), e no lugar entra
                         "Encerrado" — ou o link da gravação, se houver
                         data-agn-gravacao.
     Evento de ontem com "Inscreva-se" é o convite falso mais comum de site
     de empresa, e ninguém percebe porque ninguém volta à home para conferir.
     Mesmo método do dados/cards-cotacoes: vencido, o que afirmava sai.

     SEM JAVASCRIPT a lista aparece como foi escrita, com todos os botões:
     ali o componente não tem como saber a hora, e a responsabilidade de
     tirar o evento passado volta a quem publica. A ficha diz isso.
     ========================================================================== -->

<section class="sec-dark sec-pad agn" data-agn>
  <div class="wrap">

    <div class="agn-topo">
      <span class="eyebrow" data-rv>Agenda</span>
      <h2 class="sec-title ttl">Encontros e <em>eventos</em></h2>
      <p class="sec-lead" data-rv data-d="1">Conversas abertas com os sócios e a equipe de análise. Gratuitas, com vagas limitadas nas presenciais.</p>
    </div>

    <ul class="agn-lista" role="list">

      <li class="agn-item" data-rv
          data-agn-inicio="2026-10-14T19:00:00-03:00" data-agn-fim="2026-10-14T21:30:00-03:00">
        <time class="agn-data" datetime="2026-10-14T19:00-03:00">
          <span class="agn-dia">14</span>
          <span class="agn-mes">out</span>
          <span class="agn-sem">quarta</span>
        </time>
        <div class="agn-corpo">
          <p class="agn-formato">Presencial · São Paulo</p>
          <h3 class="agn-titulo">Perspectivas para o último trimestre</h3>
          <p class="agn-desc">Juros, câmbio e o que muda na carteira de quem vive de renda — com perguntas no fim.</p>
          <p class="agn-hora">19h às 21h30</p>
        </div>
        <div class="agn-fim">
          <a class="btn btn-ghost agn-acao" href="#inscricao-perspectivas">Inscrever-se</a>
        </div>
      </li>

      <li class="agn-item" data-rv data-d="1"
          data-agn-inicio="2026-10-23T12:30:00-03:00" data-agn-fim="2026-10-23T13:30:00-03:00">
        <time class="agn-data" datetime="2026-10-23T12:30-03:00">
          <span class="agn-dia">23</span>
          <span class="agn-mes">out</span>
          <span class="agn-sem">sexta</span>
        </time>
        <div class="agn-corpo">
          <p class="agn-formato">Online</p>
          <h3 class="agn-titulo">Sucessão em empresas familiares: o que muda em 2027</h3>
          <p class="agn-desc">Uma hora, no almoço, sobre holding, inventário e o calendário da reforma.</p>
          <p class="agn-hora">12h30 às 13h30</p>
        </div>
        <div class="agn-fim">
          <a class="btn btn-ghost agn-acao" href="#inscricao-sucessao">Inscrever-se</a>
        </div>
      </li>

      <li class="agn-item" data-rv data-d="2"
          data-agn-inicio="2026-11-06T09:00:00-03:00" data-agn-fim="2026-11-06T10:30:00-03:00">
        <time class="agn-data" datetime="2026-11-06T09:00-03:00">
          <span class="agn-dia">06</span>
          <span class="agn-mes">nov</span>
          <span class="agn-sem">sexta</span>
        </time>
        <div class="agn-corpo">
          <p class="agn-formato">Presencial · Porto Alegre</p>
          <h3 class="agn-titulo">Café com os sócios: offshore depois da nova tributação</h3>
          <p class="agn-desc">Para clientes e convidados. Doze lugares.</p>
          <p class="agn-hora">9h às 10h30</p>
        </div>
        <div class="agn-fim">
          <a class="btn btn-ghost agn-acao" href="#inscricao-cafe">Inscrever-se</a>
        </div>
      </li>

      <li class="agn-item" data-rv data-d="3"
          data-agn-inicio="2026-09-18T12:30:00-03:00" data-agn-fim="2026-09-18T13:30:00-03:00"
          data-agn-gravacao="#gravacao-setembro">
        <time class="agn-data" datetime="2026-09-18T12:30-03:00">
          <span class="agn-dia">18</span>
          <span class="agn-mes">set</span>
          <span class="agn-sem">sexta</span>
        </time>
        <div class="agn-corpo">
          <p class="agn-formato">Online</p>
          <h3 class="agn-titulo">Carta mensal comentada: setembro</h3>
          <p class="agn-desc">Os sócios leem e comentam a carta do mês.</p>
          <p class="agn-hora">12h30 às 13h30</p>
        </div>
        <div class="agn-fim">
          <a class="btn btn-ghost agn-acao" href="#inscricao-carta">Inscrever-se</a>
        </div>
      </li>

    </ul>

    <p class="agn-vazio" data-agn-vazio hidden>Nenhum encontro agendado agora. Os próximos aparecem aqui.</p>
  </div>
</section>

estilo.css · 96 linhas · 4,2 KB

/* ==========================================================================
   AGENDA DE EVENTOS — agn-
   Lista de encontros com a data grande, e o convite que expira sozinho.
   Tokens usados: --fg --fg-2 --fg-muted --rule --rule-2 --accent
                  --accent-ink --display --radius-pill --ease
   ========================================================================== */

.agn-topo{max-width:720px}

.agn-lista{margin-top:clamp(34px,5vh,52px);border-top:1px solid var(--rule)}

/* Três colunas: a data, o corpo, a ação. A data tem largura fixa para os
   dias de um e de dois dígitos alinharem pela mesma borda. */
.agn-item{
  display:grid;
  grid-template-columns:clamp(84px,9vw,120px) minmax(0,1fr) auto;
  gap:clamp(18px,3vw,44px);
  align-items:center;
  padding:clamp(22px,3vh,30px) 0;
  border-bottom:1px solid var(--rule);
}

/* ---------------------------------------------------------------- a data
   O dia é a peça gráfica: é por ele que o olho percorre a lista. Algarismos
   tabulares, para 06 e 23 ocuparem a mesma largura. */
.agn-data{display:grid;justify-items:start;line-height:1}
.agn-dia{
  font-family:var(--display);font-weight:700;
  font-size:clamp(38px,4.2vw,58px);letter-spacing:-.04em;
  font-variant-numeric:tabular-nums;color:var(--fg);
}
.agn-mes{
  margin-top:6px;
  font-family:var(--display);font-weight:700;font-size:12px;
  letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent-ink,var(--accent));
}
.agn-sem{margin-top:4px;font-size:11.5px;letter-spacing:.06em;color:var(--fg-muted)}

/* ---------------------------------------------------------------- corpo */
.agn-corpo{display:grid;gap:7px}
.agn-formato{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--fg-muted)}
.agn-titulo{
  font-family:var(--display);font-weight:700;
  font-size:clamp(18px,1.7vw,23px);line-height:1.2;letter-spacing:-.016em;
  color:var(--fg);text-wrap:pretty;
}
.agn-desc{font-size:14.5px;line-height:1.6;color:var(--fg-2);max-width:62ch}
.agn-hora{font-size:13px;color:var(--fg-muted);font-variant-numeric:tabular-nums}

/* ---------------------------------------------------------------- ação
   Os selos são criados pelo script, nunca escritos: "Acontecendo agora"
   durante o evento, "Encerrado" depois dele. */
.agn-fim{display:flex;align-items:center;justify-content:flex-end;gap:12px}
.agn-selo{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  font-family:var(--display);font-weight:700;font-size:10.5px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--fg-muted);
  border:1px solid var(--rule-2);border-radius:var(--radius-pill);padding:8px 14px;
}
.agn-item[data-estado="agora"] .agn-selo{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 50%,transparent)}
/* O ponto aceso do "agora". Pulsa por opacidade — nada escala —, e o reduce
   o deixa parado e aceso. */
.agn-item[data-estado="agora"] .agn-selo::before{
  content:"";width:7px;height:7px;border-radius:50%;background:currentColor;
  animation:agnPulso 1.6s ease-in-out infinite;
}
@keyframes agnPulso{0%,100%{opacity:1}50%{opacity:.3}}
@media(prefers-reduced-motion:reduce){
  .agn-item[data-estado="agora"] .agn-selo::before{animation:none}
}
.agn-gravacao{
  font-family:var(--display);font-weight:700;font-size:10.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--fg-2);white-space:nowrap;
  border-bottom:1px solid var(--rule-2);padding-bottom:3px;
  transition:color .25s,border-color .25s;
}
.agn-gravacao:hover{color:var(--accent-ink,var(--accent));border-color:currentColor}

/* Encerrado: o evento continua na lista como registro, com menos peso. Só o
   corpo apaga — a data segue legível, porque "quando foi" é a informação que
   sobra de um evento passado. */
.agn-item[data-estado="encerrado"] .agn-corpo{opacity:.55}
.agn-item[data-estado="encerrado"] .agn-dia{color:var(--fg-muted)}

.agn-vazio{
  padding:28px 0;border-bottom:1px solid var(--rule);
  font-size:15px;color:var(--fg-muted);
}
.agn-vazio[hidden],.agn-item[hidden]{display:none}

@media(max-width:760px){
  .agn-item{grid-template-columns:72px minmax(0,1fr);row-gap:16px;align-items:start}
  .agn-fim{grid-column:2;justify-content:flex-start}
  .agn-dia{font-size:40px}
}

script.js · 84 linhas · 3,4 KB

/* ==========================================================================
   AGENDA DE EVENTOS — agn-
   Concede o estado de cada evento a partir do relógio de quem olha, e só
   isso. Não anima nada: o ponto pulsante do "agora" é keyframe de CSS, que o
   reduce desliga.

     antes do início   intocado
     durante           data-estado="agora" + selo "Acontecendo agora"
     depois do fim     data-estado="encerrado"; o botão de inscrição SAI do
                       DOM e no lugar entra "Encerrado" — mais o link da
                       gravação, se o evento declarar data-agn-gravacao

   Por que o botão sai do DOM e não só da vista: escondido por CSS, ele
   continuaria na árvore de acessibilidade e na ordem de Tab — um leitor de
   tela ainda ofereceria "Inscrever-se" num evento de ontem. Mesmo motivo
   pelo qual o dados/cards-cotacoes tira o preço vencido do DOM.

   data-agn-passados="ocultar" na raiz esconde os encerrados em vez de
   marcá-los; se todos saírem, aparece a mensagem de agenda vazia.

   Reavalia a cada minuto enquanto a aba está aberta: quem deixa a página
   parada durante o evento vê o "agora" entrar e sair sem recarregar.
   ========================================================================== */
(function () {
  var raizes = document.querySelectorAll('[data-agn]');
  if (!raizes.length) return;                  // guard: sem o componente, saída silenciosa

  function selo(texto) {
    var s = document.createElement('span');
    s.className = 'agn-selo';
    s.textContent = texto;
    return s;
  }

  [].forEach.call(raizes, function (raiz) {
    var itens = [].slice.call(raiz.querySelectorAll('.agn-item'));
    var vazio = raiz.querySelector('[data-agn-vazio]');
    var ocultar = raiz.getAttribute('data-agn-passados') === 'ocultar';

    function avaliar() {
      var agora = Date.now(), visiveis = 0;

      itens.forEach(function (item) {
        var ini = Date.parse(item.getAttribute('data-agn-inicio'));
        var fim = Date.parse(item.getAttribute('data-agn-fim')) || ini;
        if (isNaN(ini)) { visiveis++; return; }   // sem data legível: não afirma nada
        var estado = agora >= fim ? 'encerrado' : (agora >= ini ? 'agora' : 'aberto');
        if (item.getAttribute('data-estado') === estado) {
          if (!item.hidden) visiveis++;
          return;
        }
        item.setAttribute('data-estado', estado);

        var caixa = item.querySelector('.agn-fim');
        var velho = caixa && caixa.querySelector('.agn-selo');
        if (velho) caixa.removeChild(velho);

        if (estado === 'agora' && caixa) {
          caixa.insertBefore(selo('Acontecendo agora'), caixa.firstChild);
        }
        if (estado === 'encerrado' && caixa) {
          var acao = caixa.querySelector('.agn-acao');
          if (acao) caixa.removeChild(acao);
          caixa.appendChild(selo('Encerrado'));
          var grav = item.getAttribute('data-agn-gravacao');
          if (grav && !caixa.querySelector('.agn-gravacao')) {
            var a = document.createElement('a');
            a.className = 'agn-gravacao';
            a.href = grav;
            a.textContent = 'Ver gravação';
            caixa.appendChild(a);
          }
          if (ocultar) item.hidden = true;
        }
        if (!item.hidden) visiveis++;
      });

      if (vazio) vazio.hidden = visiveis > 0;
    }

    avaliar();
    setInterval(avaliar, 60000);
  });
})();

Uma lista de encontros em que a data é a primeira coisa que o olho lê: o dia em corpo de display, o mês e o dia da semana embaixo. Ao lado, o formato (presencial, online), o título, uma linha e o horário; no fim da linha, o botão de inscrição. E o convite expira sozinho: durante o evento aparece "Acontecendo agora"; depois dele, o botão sai e no lugar entra "Encerrado" — ou o link da gravação.

O problema que ela resolve: evento de ontem com "Inscreva-se" é o convite falso mais comum em site de empresa. Ninguém volta à home para tirar o card depois do evento, e a página passa semanas convidando para uma coisa que já aconteceu — o que diz à pessoa, com precisão, que ninguém cuida daquele site.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Título da seção 2 a 6 palavras, o gênero: Agenda, Encontros e eventos
Apoio 8 a 30 palavras: para quem, quanto custa, se a vaga é limitada. Opcional
Formato Online ou Presencial · Cidade, sempre nesse formato
Título do evento 3 a 12 palavras, o assunto — nunca "Webinar"
Descrição 6 a 24 palavras, do que se fala e para quem. Opcional
Horário "19h às 21h30". Tem de bater com data-agn-inicio e data-agn-fim
Botão 1 a 3 palavras, o mesmo em toda a lista

O que mantém a peça: ordem cronológica, do mais próximo para o mais distante. Um encerrado marcado fica no fim; com a variação só os próximos, ele some.

Especificação dos assets

Nenhum. A data é a peça gráfica.


Como usar

<link rel="stylesheet" href="componentes/listas/agenda-eventos/estilo.css">
<script src="componentes/listas/agenda-eventos/script.js"></script>

Cada evento declara início e fim com fuso:

<li class="agn-item" data-agn-inicio="2026-10-14T19:00:00-03:00" data-agn-fim="2026-10-14T21:30:00-03:00">

Ganchos

Gancho Papel
[data-agn] a raiz que o script procura
data-agn-inicio / data-agn-fim ISO 8601 com fuso, por evento. É deles que o estado sai
data-agn-gravacao opcional: o link que aparece no lugar do botão depois do fim
data-agn-passados="ocultar" opcional, na raiz: esconde os encerrados
.agn-item[data-estado] aberto, agora ou encerrado, escrito pelo script
[data-agn-vazio] a mensagem que aparece quando nenhum evento sobra

Variações


Compliance


Gotchas


Movimento reduzido / toque

O único movimento é o ponto pulsante do "agora", e o reduce o deixa parado. O resto é estado, que continua sendo concedido. No toque não há nada que dependa de hover.


Histórico

Origem Quando O quê
autoral 2026-09-29 Construído do zero. Padrão de domínio comum, sem referência única

Origem. A página de eventos de gestoras, bancos privados e escritórios de advocacia — um padrão tão difundido que não tem dono. Nada foi copiado. O registro consolidado vai em projetos/autorais.md.

Método herdado. É o motor do dados/cards-cotacoes aplicado a convite: o que a peça afirma ("inscreva-se") só vale enquanto o relógio sustenta, e vencido, sai.