# Template: Página VSL — Video Sales Letter
> Página de vendas centrada em vídeo (YouTube, Vimeo ou player nativo).
> O vídeo é o principal elemento de conversão — tudo gira em torno de quem assistiu quanto.
> Infraestrutura: Cloudflare Workers + D1 (100% Native)

**Quando usar este modelo:**
- VSL (Video Sales Letter) com vídeo como principal argumento de venda
- Webclasse / Aula gratuita antes do pitch
- Lançamento com vídeo de antecipação + oferta no final
- Série de vídeos com desbloqueio progressivo

**Diferença dos outros modelos:**
- Engajamento medido por **progresso de vídeo**, não apenas por scroll
- `VideoDropout` registra o **segundo exato** em que cada usuário parou de assistir
- CTA só aparece/destrava após threshold de vídeo (ex: 75%)
- Lead capture disparado **dentro do vídeo** (ex: ao atingir 50% do progresso)

---

## Arquitetura Técnica

```
┌─────────────────────────────────────────────────────────────────┐
│ Browser: Página VSL                                             │
│                                                                 │
│  1. PageView → Worker (entrada do usuário)                      │
│  2. VideoPlay → Worker (interesse confirmado)                   │
│  3. VideoProgress 25/50/75/100 → Worker (qualificação)         │
│  4. VideoDropout → Worker (segundo exato de abandono)           │
│  5. Lead (se formulário aparecer após threshold) → Worker       │
│  6. InitiateCheckout (CTA destravado pelo vídeo) → Worker      │
└─────────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│ Cloudflare Worker                                               │
│                                                                 │
│  /track:                                                 │
│    VideoProgress 75% → Meta: ViewContent + TikTok: ViewContent  │
│    Lead → Meta: Lead + GA4: generate_lead + TikTok: SubmitForm  │
│    InitiateCheckout → Meta + GA4 + TikTok                       │
│                                                                 │
│  D1: behavioral_events (dropout_percent, dropout_second)        │
└─────────────────────────────────────────────────────────────────┘
```

---

## Passo 1 — Instalação do SDK

```html
<!-- No <head> -->
<script type="module">
  import { init, track, trackLead } from '/js/cdpTrack.js';
  import { getVideoSummary } from '/js/micro-events.js';

  window.cdpGetVideoSummary = getVideoSummary;
  await init();
</script>
```

O `initMicroEvents()` é chamado automaticamente dentro de `init()` e detecta todos os vídeos da página.

---

## Passo 2 — Marcar os Vídeos para Tracking

### YouTube Embed
```html
<!-- Adicionar id para identificação -->
<iframe
  id="vsl-main"
  src="https://www.youtube.com/embed/XXXXXXXXX?enablejsapi=1"
  data-video-id="vsl-principal"
  allow="autoplay"
  allowfullscreen>
</iframe>
```

> O `enablejsapi=1` é necessário. O SDK injeta automaticamente se não estiver na URL.

### Vimeo Embed
```html
<iframe
  id="vsl-main"
  src="https://player.vimeo.com/video/XXXXXXXXX"
  data-video-id="vsl-principal"
  allow="autoplay; fullscreen">
</iframe>
```

### Player HTML5 Nativo
```html
<video
  id="vsl-main"
  data-video-id="vsl-principal"
  controls
  preload="metadata">
  <source src="/videos/vsl.mp4" type="video/mp4">
</video>
```

---

## Passo 3 — Lógica de Desbloqueio de CTA por Progresso de Vídeo

A técnica mais eficaz: o botão de compra fica oculto e só aparece quando o usuário atinge um threshold do vídeo.

```html
<!-- CTA inicialmente oculto -->
<div id="cta-container" style="display:none; opacity:0; transition: opacity 0.5s;">
  <a href="https://pay.hotmart.com/XXXXXX" class="btn-comprar">
    Quero Garantir Minha Vaga →
  </a>
</div>

<script>
// Ouve eventos do cdpTrack para destravar o CTA
window.addEventListener('cdp:VideoProgress', (e) => {
  const { progress_percent, video_id } = e.detail;

  // Destravar CTA ao atingir 75% do vídeo
  if (progress_percent >= 75 && video_id === 'vsl-principal') {
    const cta = document.getElementById('cta-container');
    cta.style.display = 'block';
    setTimeout(() => { cta.style.opacity = '1'; }, 100);

    // Disparar ViewContent: sinal forte de intenção
    window.cdpTrack.track('ViewContent', {
      content_name: 'VSL 75% - CTA Destravado',
      content_type: 'video',
      video_progress: 75
    });
  }
});

// Formulário de lead que aparece a 50%
window.addEventListener('cdp:VideoProgress', (e) => {
  if (e.detail.progress_percent >= 50 && e.detail.video_id === 'vsl-principal') {
    document.getElementById('lead-form-container')?.classList.remove('hidden');
  }
});
</script>
```

Para emitir o evento custom, adicionar no dispatcher de `micro-events.js` (ou escutar via worker callback):

```javascript
// Em micro-events.js — dispatchEvent já chama cdpTrack.track()
// Para emitir evento DOM customizado em paralelo:
function dispatchEvent(eventName, data = {}) {
  if (typeof cdpTrack !== 'undefined' && cdpTrack.track) {
    cdpTrack.track(eventName, data);
  }
  // Evento DOM para código da página reagir
  window.dispatchEvent(new CustomEvent(`cdp:${eventName}`, { detail: data }));
}
```

---

## Passo 4 — Mapeamento de Eventos de Vídeo para Plataformas

| Evento CDP | Meta CAPI | GA4 | TikTok | Gatilho |
|---|---|---|---|---|
| VideoPlay | — | video_start | — | Usuário deu play |
| VideoProgress 25% | — | video_progress | — | Assistiu 25% |
| VideoProgress 50% | ViewContent | video_progress | ViewContent | Assistiu 50% |
| VideoProgress 75% | ViewContent | video_progress | ViewContent | Assistiu 75% — CTA aparece |
| VideoProgress 100% | ViewContent | video_complete | ViewContent | Assistiu tudo |
| VideoDropout | — | (behavioral) | — | Parou antes do fim |
| Lead | Lead | generate_lead | SubmitForm | Formulário preenchido no vídeo |
| InitiateCheckout | InitiateCheckout | begin_checkout | InitiateCheckout | Clicou no CTA |

### Configurar no Worker (`worker.js`) — handler de VideoProgress:

```javascript
// Dentro do handler /track, adicionar case para VideoProgress
case 'VideoProgress':
  const vp = payload.behavioral_data?.progress_percent || 0;
  // Só disparar plataformas em thresholds significativos
  if (vp >= 50) {
    await Promise.allSettled([
      sendMetaCapi(env, 'ViewContent', payload, {
        content_name: `VSL ${vp}%`,
        content_type: 'video',
      }),
      sendGA4(env, 'video_progress', payload, { percent: vp }),
      sendTikTokEvents(env, 'ViewContent', payload),
    ]);
  }
  // Sempre salvar no D1 (para dropout heatmap)
  await saveVideoEvent(env.DB, payload);
  break;

case 'VideoDropout':
  // Registrar no D1 para análise de abandono por minuto
  await env.DB.prepare(`
    INSERT INTO behavioral_events (uid, event_type, event_data, created_at)
    VALUES (?, 'VideoDropout', ?, datetime('now'))
  `).bind(
    payload.user_id,
    JSON.stringify({
      video_id: payload.behavioral_data.video_id,
      dropout_percent: payload.behavioral_data.dropout_percent,
      dropout_second: payload.behavioral_data.dropout_second,
    })
  ).run();
  break;
```

---

## Passo 5 — Dashboard de Dropout Heatmap

Consulta D1 para ver onde os usuários abandonam o vídeo:

```sql
-- Distribuição de abandono por segmento de 10%
SELECT
  ROUND(CAST(json_extract(event_data, '$.dropout_percent') AS INTEGER) / 10.0) * 10 AS percent_bucket,
  COUNT(*) AS abandonos,
  AVG(json_extract(event_data, '$.dropout_second')) AS media_segundo
FROM behavioral_events
WHERE event_type = 'VideoDropout'
  AND json_extract(event_data, '$.video_id') = 'vsl-principal'
  AND created_at >= datetime('now', '-7 days')
GROUP BY percent_bucket
ORDER BY percent_bucket;

-- Taxa de conclusão por cohort de tráfego
SELECT
  l.utm_source,
  COUNT(DISTINCT b.uid) AS viewers,
  SUM(CASE WHEN json_extract(b.event_data, '$.dropout_percent') >= 75 THEN 1 ELSE 0 END) AS chegaram_75,
  ROUND(
    100.0 * SUM(CASE WHEN json_extract(b.event_data, '$.dropout_percent') >= 75 THEN 1 ELSE 0 END)
    / COUNT(DISTINCT b.uid), 1
  ) AS taxa_conclusao_75pct
FROM behavioral_events b
LEFT JOIN leads l ON l.user_id = b.uid
WHERE b.event_type = 'VideoDropout'
  AND b.created_at >= datetime('now', '-30 days')
GROUP BY l.utm_source
ORDER BY viewers DESC;
```

---

## Checklist de Verificação

```
[ ] SDK instalado no <head> com type="module"
[ ] Vídeo com id ou data-video-id para identificação
[ ] enablejsapi=1 na URL do YouTube (ou deixar o SDK injetar)
[ ] CTA oculto inicialmente, destravado por VideoProgress
[ ] worker.js: case 'VideoProgress' e 'VideoDropout' implementados
[ ] D1: tabela behavioral_events existente (migrate-v2.sql ou superior)
[ ] Testar: abrir o vídeo, pausar a 30%, verificar VideoDropout no D1
[ ] Testar: assistir até 75%, verificar CTA aparecendo + ViewContent no D1
[ ] Query de dropout heatmap retornando dados
```

---

## Fluxo Completo de Uma Sessão VSL

```
1. Usuário chega via anúncio Meta
   └─ cdp_uid gerado (cookie 365 dias)
   └─ UTMs salvos (_cdp_aff no localStorage — fallback de afiliado)
   └─ PageView → Worker → D1 + Meta PageView + GA4 page_view

2. Usuário dá play no vídeo
   └─ VideoPlay → Worker → D1 behavioral_events

3. Usuário assiste até 50%
   └─ VideoProgress 25% → Worker
   └─ VideoProgress 50% → Worker → Meta ViewContent + Formulário de lead aparece

4. Usuário para de assistir a 62% (abandono)
   └─ VideoPause → VideoDropout(62%, segundo 743) → Worker → D1

5. (Outro cenário) Usuário assiste até 75%
   └─ VideoProgress 75% → Worker → Meta ViewContent + CTA destrava
   └─ Usuário clica no CTA → InitiateCheckout → Worker
   └─ Usuário compra na Hotmart → webhook → Worker → Meta Purchase CAPI

6. Otimização
   └─ Dashboard mostra: 68% dos usuários abandonam entre 45-60% do vídeo
   └─ Ação: testar pitch antecipado no minuto 40
```
