383 lines
9.8 KiB
Markdown
Raw Normal View History

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