Skip to content
AndrijoPublic

About

Bentoboxd turns any Letterboxd feed into a minimalist bento dashboard: metrics, charts, and alternative views of recent movie activity, plus quiz and higher-or-lower games.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BENTOBOXD

Dashboard analítico visual para Letterboxd

React Vite License

Descripción

Bentoboxd es una aplicación web que convierte el feed de Letterboxd de cualquier usuario en un panel de visualización interactivo con una estética editorial minimalista de paleta restringida. Ingresa un usuario y obtén métricas, gráficos y vistas alternativas de su actividad cinematográfica reciente.

Nace como un experimento personal para explorar visualización de datos, integración de APIs y animaciones CSS, todo en una interfaz sobria de cinco tonos. Sin frameworks de UI, sin TypeScript, sin dependencias pesadas — solo React, Vite y CSS.

Características

  • Feed RSS de Letterboxd — Ingresa un nombre de usuario o URL y obtén sus últimas 50 películas vistas (las listas del feed se ignoran). Las vistas muestran las 20 más recientes y «Ver todas» abre las 50 con filtros por me gusta, nota y año; las estadísticas y los juegos siempre usan las 50. Letterboxd no publica más en el feed, así que las cifras no cubren el historial completo
  • 3 modos de visualización:
    • Bento — Cuadrícula dinámica sin huecos con tarjetas 1x1, 2x1, 1x2 y 2x2 (en móvil, 3 columnas sin 1x2)
    • Línea de tiempo — Lista cronológica con pósters, calificaciones y fechas
    • Terminal — Listado estilo terminal retro
  • Datos clave del perfil — Una frase generada con su ritmo, género principal y notas, seguida de promedio, tiempo viendo, comparación con el público (TMDB) y películas por semana
  • Paneles de análisis (con un índice fijo para saltar entre ellos):
    • Resumen — Me gusta, vistas otra vez, día más activo, década más vista y distribución de calificaciones
    • Evolución mensual — Películas y nota media por mes, según la fecha en que se vio cada una
    • Opiniones impopulares — Las películas con mayor diferencia frente al público (TMDB), a favor y en contra, con «Cómo se calcula»
    • Mejor calificados — Géneros y directores con el mejor promedio de calificaciones del perfil (mínimo 2 películas)
    • Géneros — 4 tipos de gráfico (Dona, Barras, Matriz, Medidores)
    • Directores — Quienes tienen al menos 2 películas, en vista de lista con foto u holograma
    • Reparto — Cuadrícula con fotos
    • Países de producción — Lista o sectores con banderas
    • Plataformas de streaming — Disponibilidad en México (datos de JustWatch)
  • Explorar los datos — Al pulsar un género, director, actor, país o plataforma se despliegan sus películas, enlazadas a Letterboxd
  • Juegos (páginas propias, enlazables y compatibles con el botón atrás):
    • Higher or Lower (#higher-lower) — Adivina si la siguiente película tiene mayor/menor rating, popularidad o año (en popularidad solo se muestra cuál gana)
    • Quiz (#quiz) — 5 preguntas sobre calificación, año de estreno, dirección o póster
  • Skeleton loading mientras cargan el feed y los metadatos de TMDB
  • Tema claro y oscuro — Botón de cambio en la cabecera; respeta prefers-color-scheme en la primera visita y recuerda tu elección en localStorage (sin parpadeo al cargar)
  • Español e inglés — Selector ES/EN en la cabecera; usa el idioma del navegador en la primera visita, recuerda tu elección en localStorage y muestra los títulos en español cuando TMDB los tiene
  • Paleta de cinco tonos — Todos los colores salen de tokens CSS (tintas y crema); los gráficos usan rampas derivadas de ellos
  • Diseño responsive con layout adaptado a móvil
  • Accesibilidad — Etiquetas ARIA, anuncios en vivo para los juegos y enlaces externos señalados para lectores de pantalla
  • Enlaces compartibles — El perfil va en la URL (?user=<usuario>, combinable con #quiz o #higher-lower): se puede compartir con «Copiar enlace», recargar y navegar con atrás/adelante entre perfiles
  • Persistencia — Últimos 3 perfiles guardados en localStorage, disponibles también desde la búsqueda

Stack

Tecnología Versión
React 19
Vite 7
ESLint (flat config) 9
CSS Vanilla + design tokens
Tipografías Milker, DM Sans (autoalojadas)
Tests Vitest 3 + Testing Library
APIs Letterboxd RSS, TMDB v3

Requisitos

  • Node.js (LTS recomendada)
  • npm

Instalación

git clone https://github.com/Andrijo/Bentoboxd.git
cd Bentoboxd
npm install

Configuración

Copia .env.example a .env y completa las variables:

TMDB_API_KEY=tu_api_key_de_tmdb
# Solo producción (ver Despliegue)
# VITE_RSS_PROXY_URL=/api/rss

La key de TMDB nunca llega al navegador: el cliente llama a /api/tmdb/... y el proxy (Vite en local, api/tmdb.js en producción) la añade del lado del servidor. Solo se permiten los endpoints que usa la app (search/movie y movie/<id>).

Obtén una API key gratuita en themoviedb.org.

Uso

npm run dev        # Servidor de desarrollo → http://localhost:5173
npm run build      # Build de producción → dist/
npm run preview    # Previsualizar build
npm run lint       # Ejecutar ESLint
npm run test       # Ejecutar tests (Vitest, modo watch)
npm run test:run   # Ejecutar tests una vez (CI)

Los tests usan Vitest + Testing Library (jsdom) y viven junto al código que prueban (*.test.js/*.test.jsx).

CI

GitHub Actions (.github/workflows/ci.yml) ejecuta lint, test:run y build con Node 22 en cada push a main y en cada pull request.

Despliegue (Vercel)

El RSS de Letterboxd no permite CORS y la key de TMDB no debe ir en el bundle, así que en producción la app usa dos funciones serverless listas para Vercel: api/rss.js y api/tmdb.js (enrutada por vercel.json).

  1. Importa el repositorio en Vercel (detecta Vite automáticamente).
  2. En Settings → Environment Variables define:
    • TMDB_API_KEY — tu key de TMDB. Si antes usabas VITE_TMDB_API_KEY, bórrala: ya no se usa y el prefijo VITE_ la expondría.
    • VITE_RSS_PROXY_URL — opcional: en producción se usa /api/rss por defecto. Solo cámbialo si usas otro proxy.
  3. Deploy. La app llamará a /api/rss?user=<usuario> y a /api/tmdb/....

Si ves un error 401 de TMDB, la variable TMDB_API_KEY no está definida (o es inválida) en el entorno de las funciones de Vercel. Corrígela y haz Redeploy.

En local solo necesitas TMDB_API_KEY en .env: npm run dev usa los proxies de vite.config.js (/rss-proxy y /api/tmdb).

Como la key ya no se compila en el bundle, rótala en TMDB si la versión anterior estuvo publicada.

En otro hosting, apunta VITE_RSS_PROXY_URL a tu propio proxy. Por defecto se le pasa ?user=<usuario>; si la URL contiene {user}, se usa en modo path-style.

Seguridad

  • Cabeceras (vercel.json): CSP estricta (solo scripts propios; imágenes de Letterboxd, TMDB y flagcdn), frame-ancestors 'none', nosniff, Referrer-Policy y Permissions-Policy. El script que aplica el tema vive en public/theme-init.js para no necesitar 'unsafe-inline'.
  • API (api/*): las respuestas llevan Content-Security-Policy: default-src 'none'; sandbox y un límite de peticiones por IP (20/min en el RSS, 240/min en TMDB). El límite es en memoria por instancia: para algo estricto, añade una regla de rate limit en el firewall de Vercel.
  • CORS: /api/rss solo responde al mismo origen. Si sirves el frontend desde otro dominio, define RSS_ALLOWED_ORIGIN con ese origen.
  • Si mueves algún proxy a otro dominio (VITE_RSS_PROXY_URL o VITE_TMDB_PROXY_URL absolutos), añádelo a connect-src en la CSP de vercel.json.

Estructura

api/
├── rss.js                   # Proxy serverless del RSS (Vercel)
├── tmdb.js                  # Proxy serverless de TMDB (Vercel)
└── _lib/
    ├── apiHeaders.js        # Cabeceras de seguridad y respuesta 429
    ├── rateLimit.js         # Límite de peticiones por IP
    └── tmdbUpstream.js      # Rutas permitidas de TMDB (prod y dev)

src/
├── App.jsx                  # Componente principal
├── HighLowGame.jsx          # Página Higher or Lower (#higher-lower)
├── main.jsx                 # Punto de entrada
├── index.css                # Estilos globales
├── components/              # Componentes React
│   ├── AppHeader.jsx        # Cabecera y navegación
│   ├── AppFooter.jsx        # Pie con atribución (Letterboxd, TMDB)
│   ├── SearchSection.jsx    # Búsqueda de usuario
│   ├── RecentProfiles.jsx   # Perfiles recientes
│   ├── EmptyLanding.jsx     # Pantalla inicial sin perfil
│   ├── ProfileOverview.jsx  # Titular, datos clave y «Copiar enlace»
│   ├── SectionNav.jsx       # Índice fijo de secciones
│   ├── Panel/               # Panel y SegmentedControl compartidos
│   ├── MovieViews.jsx       # Bento / Línea de tiempo / Terminal
│   ├── MovieFilters.jsx     # Filtros de «Ver todas»
│   ├── MovieDrilldown.jsx   # Películas de un género, persona, país o plataforma
│   ├── DataPanels.jsx       # Contenedor de paneles de análisis
│   ├── HowCalculated.jsx    # Explicación desplegable «Cómo se calcula»
│   ├── Skeleton.jsx         # Placeholders de carga
│   ├── PosterImage.jsx      # Póster con carga diferida
│   ├── PersonAvatar.jsx     # Foto o iniciales de una persona
│   ├── StarRating.jsx       # Calificación en estrellas
│   ├── KeepAlive.jsx        # Conserva el estado de los juegos entre pestañas
│   ├── ErrorBoundary.jsx    # Pantalla de error inesperado
│   ├── ErrorNotice.jsx      # Aviso de error con reintento
│   ├── BentoGrid/           # Cuadrícula bento
│   ├── StatsSummary/        # Resumen e histograma de calificaciones
│   ├── MonthlyPanel/        # Evolución mensual
│   ├── GenrePanel/          # Gráficos de géneros
│   ├── DirectorsPanel/      # Paneles de directores
│   ├── ActorsPanel/         # Paneles de actores
│   ├── CountriesPanel/      # Paneles de países
│   ├── PlatformsPanel/      # Plataformas de streaming
│   ├── TopRatedPanel/       # Mejor calificados del perfil
│   ├── OpinionsPanel/       # Opiniones impopulares (vs TMDB)
│   └── Quiz/                # Página de quiz (#quiz)
├── hooks/                   # Custom hooks
│   ├── useLetterboxdFeed.js # Fetch y parse de RSS
│   ├── useTMDBMetadata.js   # Fetch de metadatos TMDB
│   ├── useHashRoute.js      # Páginas por hash (#quiz, #higher-lower)
│   ├── useGridColumns.js    # Columnas según el ancho disponible
│   ├── useTheme.js          # Tema claro/oscuro con persistencia
│   └── useOverscrollColor.js # Color del rebote de overscroll
├── services/                # Servicios API
│   ├── letterboxd.js        # Parseo de RSS
│   └── tmdb.js              # Cliente TMDB
├── constants/               # Constantes y mapeos
├── i18n/                    # Idioma activo y diccionarios (es, en)
├── test/setup.js            # Setup de Vitest (jest-dom)
└── utils/                   # Utilidades

Licencia

El código se publica bajo la licencia MIT.

La tipografía Milker (public/fonts/Milker.woff2) no está incluida en esa licencia. Es © 2023 Brandsemut, conserva sus propios términos y su autor la ofrece gratis solo para uso personal: el uso comercial o promocional requiere una licencia aparte.

About

Bentoboxd turns any Letterboxd feed into a minimalist bento dashboard: metrics, charts, and alternative views of recent movie activity, plus quiz and higher-or-lower games.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages