Jesús Pérez d59644b96f
feat: unified auth model, project onboarding, install pipeline, config management
The full scope across this batch: POST /sessions key→token exchange, SessionStore dual-index with revoke_by_id, CLI Bearer injection (ONTOREF_TOKEN), ontoref setup
  --gen-keys, install scripts, daemon config form roundtrip, ADR-004/005, on+re self-description update (fully-self-described), and landing page refresh.
2026-03-13 20:56:31 +00:00

9.8 KiB
Raw Blame 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:

<ProjectionToggle />

O en 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:
    :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:

<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)

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

Más Lento (150ms por año)

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

Desde otro año (ej: experiencia con Rust)

<YearsCounter :startYear="2019" :speed="100" />

Con delay personalizado (iniciar 5 segundos después)

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

Sin delay (iniciar inmediatamente cuando sea visible)

<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,11,0.31,1

🖼️ Estructura del 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

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:

---
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:

<!-- 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:

<!-- 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:

<!-- 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