Compass: Un kit de inicio para cursos en línea

Adam Wathan

Últimamente he tenido muchas ganas de volver a hacer screencasts y enseñar, así que hace un par de meses preparé este minicurso gratuito Construye UIs que no apesten. A la gente pareció gustarle y ahora quiero hacer algunos proyectos de video más grandes, pero había un problema.

Soy desarrollador de software.

Y como desarrollador de software, al igual que necesito escribir mi propio generador de sitios estáticos antes de poder crear mi propio sitio web personal, necesitaba construir mi propia plataforma de cursos antes de poder siquiera pensar en preparar un curso en video.

Pues bien, seis semanas después, acabamos de lanzar Compass, un kit de inicio que diseñamos con Tailwind CSS y Next.js para publicar tu propio curso en línea.

Compass

Echa un vistazo a la vista previa en vivo como siempre para ver la experiencia completa.

Nos divertimos mucho trabajando en este proyecto, y pudimos jugar con un montón de herramientas y tecnologías nuevas y resolver algunos problemas interesantes.


Video picture-in-picture (imagen en imagen)

Puede que no lo hayas visto antes, pero la mayoría de los navegadores modernos tienen soporte nativo de Picture-in-Picture. Estas APIs te permiten tomar un video y mostrarlo en una ventana separada que el usuario puede mover y redimensionar mientras sigue navegando por el sitio.

Tuvimos esta idea para Compass donde, si empezabas a reproducir un video del curso y te desplazabas hacia abajo en la página, el video se movería a la parte inferior derecha de la pantalla para que pudieras seguir viéndolo mientras leías el contenido de la página.

Esperábamos usar las APIs de Picture-in-Picture para esto, pero resulta que no puedes activarlas de forma pasiva al hacer scroll (supongo que por razones de privacidad/seguridad); el usuario tiene que hacer clic en algo, de manera similar a las APIs del portapapeles.

En su lugar, terminamos creando nuestra propia solución sencilla, utilizando IntersectionObserver, eventos de video y un poco de estado para detectar cuándo el video se estaba reproduciendo y estaba fuera de la pantalla, y añadimos atributos de datos al elemento <video> para poder apuntar a ellos con clases de utilidad:

video-player.tsx
<video  data-offscreen={isOffscreen ? "" : undefined}  data-playing={isPlaying ? "" : undefined}  className="data-offscreen:data-playing:fixed data-offscreen:data-playing:right-4 data-offscreen:data-playing:bottom-4"/>

Me encanta usar atributos de datos para este tipo de cosas, me parece que el código se lee mucho más como el estilo estándar de Tailwind en comparación con un montón de ternarios y lógica condicional en JavaScript.


Parsear archivos VTT en transcripciones

Hay una sección en Compass para entrevistas, que incluye un video en la parte superior de la página pero también una interfaz de usuario de transcripción bastante personalizada a continuación:

Transcripción estilizada de una entrevista entre Tom Harris and Annie King

Al principio no estábamos muy seguros de cómo abordar esto. Nuestra primera idea fue simplemente escribir algunos componentes de React para estas partes y colocar el contenido directamente en un archivo MDX, pero se sentía poco realista esperar que alguien creara sus transcripciones en este formato totalmente personalizado.

Luego tuve la idea de usar un formato de archivo de subtítulos estándar, como SRT. El problema era que, para nuestras necesidades, necesitábamos codificar al hablante actual (Tom o Annie en la captura de pantalla anterior), y SRT no tiene una forma estandarizada de codificar esa información en el archivo.

Investigué un poco más y finalmente llegué a WebVTT, que es similar to SRT pero sí admite información del hablante y está diseñado convenientemente para la web.

annie-king.vtt
WEBVTT00:00.000 --> 00:20.000<v Tom Harris>Hola compañeros pasajeros, bienvenidos al podcast Compass. Hoy, tenemos una invitada especial, Annie King. Ella es la autora de The Inevitable You: How to Embrace Your Path and Succeed with Relentless Precision. Annie, bienvenida al programa.00:20.000 --> 00:35.000<v Annie King>¡Gracias! Estoy muy feliz de estar aquí. Y gracias por enviarme las preguntas por adelantado — estoy muy emocionada de compartir algunas de las ideas del libro con sus espectadores. Creo que nos vamos a divertir mucho desempacando lo que significa abrazar verdaderamente tu camino.00:35.000 --> 00:45.000<v Tom Harris>¡Absolutamente! Quiero entrar en tu libro, pero primero tengo que preguntar — ¿cómo fue crecer en un hogar que trataba la organización casi como... un deporte?

Así que conectamos las cosas para parsear los datos de la transcripción de un archivo .vtt y luego mapear esos datos para renderizarlos como una interfaz de usuario personalizada con React:

src/interview/[slug]/page.tsx
<div>  {transcript.map(({ start, speaker, text }) => (    <div key={start} className="col-span-2 grid grid-cols-subgrid items-baseline">      <TimestampButton start={start} videoId="video" className="justify-self-end" />      <div>        <p className="text-sm/7 font-semibold text-gray-950 dark:text-white">{speaker}</p>        {text.map((p, index) => (          <p key={index} className="mt-2 text-sm/7 whitespace-pre-wrap text-gray-700 dark:text-gray-400">            {p}          </p>        ))}      </div>    </div>  ))}</div>

Resultó ser bastante genial: puedo imaginar a alguien simplemente generando una transcripción en formato VTT usando IA, metiéndola en el proyecto y automáticamente se renderizará en una bonita interfaz de usuario personalizada.


Extender imágenes de Markdown para modo oscuro y cambios de diseño (layout shift)

El contenido de ejemplo que ideamos para Compass incluye muchos diagramas que queríamos adaptar entre el modo claro y oscuro.

Ejemplo de un diagrama que utiliza diferentes colores para el modo claro y oscuro

Puedes hacer este tipo de cosas de muchísimas maneras diferentes (la etiqueta <picture> lo admite de forma nativa, por ejemplo), pero realmente queríamos que los archivos de contenido se sintieran lo más parecidos posible a Markdown clásico.

Así que se nos ocurrió esta idea de un marcador de posición {scheme} en la URL de la imagen que reemplazamos con light o dark dinámicamente, cargando la imagen correcta según el esquema de colores del usuario:

## El Mito del Libre Albedrío![Prueba Neurológica](/img/neuro-proof.{scheme}.png)Tu cerebro toma decisiones antes de que seas consciente de ellas.

Ahora, el componente de imagen subyacente renderizará automáticamente neuro-proof.light.png o neuro-proof.dark.png según el esquema de colores actual. Y si solo quieres usar la misma imagen en ambos, simplemente no incluyas {scheme} in la URL en absoluto.

También queríamos evitar el cambio de diseño (layout shift) en el contenido, lo cual se puede hacer con bastante facilidad hoy en día asegurándote de dar a tus imágenes los atributos width y height para que el navegador pueda calcular la relación de aspecto y reservar espacio para la imagen cuando se cargue.

No hay una forma estándar de expresar esto en la sintaxis de imágenes de Markdown, pero después de investigar un poco descubrimos algunos precedentes en Obsidian.

Obsidian añade las dimensiones de la imagen al texto alternativo (alt), de esta manera:

## El Mito del Libre Albedrío![Prueba Neurológica|2000x990](/img/neuro-proof.{scheme}.png)Tu cerebro toma decisiones antes de que seas consciente de ellas.

Así que usamos un componente MDX personalizado para extraer las dimensiones usando el mismo formato y añadirlas a la imagen para asegurarnos de que no haya cambios de diseño a medida que se carga el contenido.


¡Así que ahí lo tienes: eso es Compass! Como siempre, es una actualización gratuita para cualquier persona con una licencia de Tailwind Plus, así que ve y descarga la base de código, explora un poco y diviértete con ella.

Si aún no tienes una licencia de Tailwind Plus, ¡considera obtener una! Comprar una es la mejor manera de apoyar nuestro trabajo en Tailwind CSS y hay un montón de cosas útiles en ella.

¡Espero usar esta plantilla para algunos proyectos futuros en los próximos meses!

Recibe todas nuestras actualizaciones directamente en tu bandeja de entrada.
Suscríbete a nuestro boletín.

Copyright © 2026 Tailwind Labs Inc.·Política de marcas registradas