# Componentes Personalizados para Slidev

## ProjectionToggle - Toggle para Modo Proyector

### 📍 Ubicación
`slides/components/ProjectionToggle.vue`

### 🎯 Descripción
Botón flotante para cambiar entre modo Monitor (oscuro) y modo Proyector (más claro) durante la presentación.

### ✨ Características

1. **Toggle visual**: Botón flotante con iconos 🖥️ / 📽️
2. **Persistencia**: Guarda la preferencia en localStorage
3. **Feedback visual**: Cambia de color según el modo activo
4. **No invasivo**: Se oculta automáticamente en modo presentador y al imprimir
5. **CSS Variables**: Usa variables CSS para cambios instantáneos

### 📝 Uso

Agrégalo a tu primera slide o a todas las slides principales:

```vue
<ProjectionToggle />
```

O en markdown:

```markdown
---
# Primera Slide
---

<ProjectionToggle />

# Tu contenido aquí...
```

### 🎨 Estados Visuales

**Monitor Mode** (por defecto)
- Icono: 🖥️ Monitor
- Color: Naranja (gradiente Rust)
- Fondo: #1c1c1c (muy oscuro)

**Projection Mode**
- Icono: 📽️ Projection
- Color: Verde (#48bb78)
- Fondo: #2d3748 (dark pero más visible)

### 💡 ¿Por qué dos modos?

Los proyectores tienen menor contraste que los monitores. El modo Projection:
- Fondo más claro (#2d3748 vs #1c1c1c)
- Mayor contraste de texto
- Bordes más gruesos en componentes
- Mejor visibilidad en salas con luz ambiente

### 🔧 Cómo Funciona

1. Click en el botón
2. Agrega/quita la clase `projection-mode` al `<html>`
3. Las CSS variables cambian automáticamente:
   ```css
   :root {
     --bg-primary: #1c1c1c;  /* Monitor */
   }

   .projection-mode {
     --bg-primary: #2d3748;  /* Projection */
   }
   ```
4. Guarda la preferencia en `localStorage`

### 📖 Documentación Completa

Ver: `slides/PROJECTION_MODE.md` para guía detallada sobre:
- Todos los métodos de activación
- Comparación visual
- Personalización de colores
- Troubleshooting

### 🎯 Recomendación de Uso

Antes de tu presentación:
1. Agrega `<ProjectionToggle />` a la primera slide
2. Durante el soundcheck con el proyector, prueba ambos modos
3. El modo elegido se guardará automáticamente

---

## YearsCounter - Contador Animado de Años

### 📍 Ubicación
`slides/components/YearsCounter.vue`

### 🎯 Descripción
Componente SVG animado que muestra un contador de años pasando como hojas de calendario, desde 1 hasta el año actual.

### ✨ Características

1. **Animación de contador**: Los números pasan de 1 hasta 40 (en 2025)
2. **Efecto de hoja**: Animación de scale que simula una página de calendario volteándose
3. **Badge animado**: Fondo con gradiente verde y efecto de brillo pulsante
4. **SVG puro**: Más ligero y eficiente que componentes complejos
5. **Totalmente personalizable**: Props para ajustar año inicial y velocidad

### 📝 Uso

En cualquier slide de Slidev:

```vue
<YearsCounter :startYear="1985" :speed="80" />
```

### 🎛️ Props

| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| `startYear` | Number | 1985 | Año desde el que se cuenta |
| `speed` | Number | 80 | Velocidad en milisegundos por año (menor = más rápido) |
| `delay` | Number | 3000 | Delay en ms antes de iniciar la animación (después de que la slide sea visible) |

### 🔧 Ejemplos de Configuración

#### Más Rápido (50ms por año)
```vue
<YearsCounter :startYear="1985" :speed="50" />
```

#### Más Lento (150ms por año)
```vue
<YearsCounter :startYear="1985" :speed="150" />
```

#### Desde otro año (ej: experiencia con Rust)
```vue
<YearsCounter :startYear="2019" :speed="100" />
```

#### Con delay personalizado (iniciar 5 segundos después)
```vue
<YearsCounter :startYear="1985" :speed="80" :delay="5000" />
```

#### Sin delay (iniciar inmediatamente cuando sea visible)
```vue
<YearsCounter :startYear="1985" :speed="80" :delay="0" />
```

### 🎨 Personalización del Estilo

El componente usa estas características visuales:

1. **Badge Verde**:
   - Gradiente: `rgba(72,187,120,0.2)` → `rgba(72,187,120,0.4)`
   - Borde: `#48bb78` (verde Rust accent)
   - Bordes redondeados: `15px`

2. **Número**:
   - Color: `#4ade80` (verde brillante)
   - Tamaño: `48px`
   - Fondo oscuro con opacidad

3. **Animaciones**:
   - **Pulso del badge**: 3 segundos, brillo entre 0.3 y 0.6 opacidad
   - **Flip del número**: 0.3 segundos, scale de `1,1` → `1,0.3` → `1,1`

### 🖼️ Estructura del SVG

```svg
<svg width="300" height="120">
  <!-- Badge con gradiente y animación -->
  <rect with gradient + glow filter + pulse animation />

  <!-- Emoji -->
  <text>💻</text>

  <!-- Contenedor del número -->
  <g>
    <rect (fondo oscuro) />
    <text (número con animación de flip) />
  </g>

  <!-- Texto descriptivo -->
  <text>años como desarrollador</text>
</svg>
```

### 🔄 Cómo Funciona la Animación

#### Detección de Visibilidad (Nuevo)

1. **IntersectionObserver**: Monitorea cuando la slide es visible (30% threshold)
2. **Activación única**: Solo se inicia la primera vez que aparece
3. **Delay configurable**: Espera 3 segundos (por defecto) antes de iniciar
4. **Compatible**: Funciona tanto en Slidev como en navegación normal

#### Secuencia de la Animación

1. **Slide visible**: IntersectionObserver detecta que el componente es visible
2. **Delay**: Espera el tiempo configurado (por defecto 3000ms)
3. **Inicio**: Comienza el contador desde 1
4. **Incremento**: Cada `speed` milisegundos, incrementa el contador
5. **Efecto flip**: Cada cambio de número aplica una animación de scale en el eje Y
6. **Fin**: Cuando alcanza el año objetivo (2025-1985=40), detiene el intervalo

**Ejemplo de timing completo**:
- Llegas a la slide 2 → espera 3 segundos → cuenta de 1 a 40 en 3.2 segundos = **6.2 segundos total**

### 💻 Código JavaScript

```javascript
const counterElement = ref(null) // Referencia al elemento DOM
const displayYear = ref(1)
const hasStarted = ref(false) // Prevenir múltiples inicios
const targetYear = new Date().getFullYear() - props.startYear // 2025 - 1985 = 40

const startAnimation = () => {
  if (hasStarted.value) return // Solo iniciar una vez

  hasStarted.value = true

  // Esperar el delay configurado antes de iniciar
  setTimeout(() => {
    let year = 1
    const interval = setInterval(() => {
      if (year <= targetYear) {
        displayYear.value = year
        year++
      } else {
        clearInterval(interval)
      }
    }, props.speed)
  }, props.delay)
}

onMounted(async () => {
  await nextTick()

  // Observar cuando el componente es visible
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && !hasStarted.value) {
          startAnimation()
          observer.disconnect() // Dejar de observar después de iniciar
        }
      })
    },
    { threshold: 0.3 } // 30% visible
  )

  observer.observe(counterElement.value)
})
```

### 🎬 Resultado

Cuando navegas a la slide:
1. **Espera 3 segundos** (delay configurable)
2. **Inicia en 1**
3. **Va incrementando** cada 80ms
4. **Animación de flip** en cada cambio
5. **Termina en 40** (años desde 1985 hasta 2025)

**Duración total**:
- Delay: 3 segundos
- Animación: 40 años × 80ms = 3.2 segundos
- **Total: 6.2 segundos**

### ✨ Comportamiento Inteligente

- ✅ **Solo se inicia cuando la slide es visible** (no al cargar la presentación)
- ✅ **Delay de 3 segundos** para dar tiempo al usuario a leer el título
- ✅ **Se inicia solo una vez** (aunque vuelvas a la slide, no se reinicia)
- ✅ **Compatible con navegación** hacia adelante y atrás en Slidev

### 📱 Responsive

El SVG se adapta automáticamente al contenedor y mantiene su aspecto.

### 🔄 Actualización Automática

El cálculo `new Date().getFullYear() - props.startYear` se hace al montar el componente, por lo que:
- En **2025**: muestra de 1 a 40
- En **2026**: mostrará de 1 a 41
- Y así sucesivamente

### 🎯 Implementación en el Slide

**Slide 2** - Introducción Personal:

```markdown
---
layout: intro
---

# Jesús Pérez Lorenzo

<div class="flex items-center gap-11 mt-8">
<div class="leading-8 opacity-80">

<YearsCounter :startYear="1985" :speed="80" />

<span class="orange">Rust</span> Developer & Cloud Architect<br>
...
</div>
</div>
```

### 🛠️ Modificar la Animación

Para cambiar el tipo de animación, edita la sección `<animateTransform>` en el componente:

```xml
<!-- Flip horizontal en lugar de vertical -->
<animateTransform
  attributeName="transform"
  type="scale"
  values="1,1; 0.3,1; 1,1"
  dur="0.3s"/>

<!-- Rotación -->
<animateTransform
  attributeName="transform"
  type="rotate"
  values="0 120 75; 360 120 75"
  dur="0.5s"/>
```

### 🎨 Cambiar Colores

Para cambiar el esquema de colores, modifica:

```xml
<!-- Verde → Naranja Rust -->
<stop offset="0%" style="stop-color:rgba(247,76,0,0.2)" />
<stop offset="100%" style="stop-color:rgba(247,76,0,0.4)" />

<!-- Color del número -->
<text fill="#ff6b4a">

<!-- Color del borde -->
<rect stroke="#f74c00" />
```

## 🚀 Crear Otros Contadores

Puedes reutilizar el componente para otras métricas:

```vue
<!-- Años con Rust -->
<YearsCounter :startYear="2019" :speed="100" />

<!-- Años en Cloud -->
<YearsCounter :startYear="2013" :speed="100" />
```

## 📦 Archivos del Componente

```
slides/
├── components/
│   ├── YearsCounter.vue    ← El componente
│   └── README.md            ← Esta documentación
└── slides.md                ← Uso del componente
```

## 🔍 Troubleshooting

### El componente no se muestra
- Asegúrate de que el archivo esté en `slides/components/`
- Slidev detecta automáticamente componentes en esta carpeta

### La animación es muy rápida/lenta
- Ajusta el prop `:speed` (en milisegundos)
- Valores recomendados: 50-150ms

### El número no llega al año correcto
- Verifica que `startYear` sea correcto
- El cálculo es: `new Date().getFullYear() - startYear`
