Colofón · Debajo del capó

El sistema
detrás del estudio.

Muntin Digital es un estudio de una sola persona, lo que significa que todo lo que ves en este sitio — el código, la tipografía, la marca, las traducciones, la narración en audio — lo construyó, lo publicó y lo mantiene una sola persona. Esta página muestra el trabajo.

El stack

Deliberadamente
pequeño.

Cero frameworks, cero paso de build para el propio HTML, cero renderizado del lado del cliente. El stack le cabe a una persona en la cabeza, despliega en menos de un minuto y corre en una capa gratuita.

  • Hosting Cloudflare Pages HTML estático servido desde el edge. ~30 ms de TTFB desde el DMV, ~80 ms desde Europa.
  • Rutas dinámicas Cloudflare Workers Los endpoints de las herramientas (diagnóstico de restaurante, APIs de calificadores) corren en el edge con caché en KV. Un solo src/worker.js.
  • HTML Escrito a mano Cada página es un index.html plano. El nav y el footer se sincronizan desde _includes/ con un script de Node de 240 líneas en cada deploy.
  • CSS Una hoja, con tokens Un solo site.css con tokens de diseño en :root para color, tipografía, espaciado, radio y movimiento. Sin preprocesador.
  • JS Un solo archivo Un solo site.js, con defer, sin framework. Aloja el reproductor de audio, el menú para compartir, la detección de idioma y la validación de formularios.
  • Tipografía Auto-hospedada Fraunces (display) e Inter (texto) servidas desde /assets/fonts/ como woff2 con fallbacks de métricas emparejadas. Cero viaje a Google Fonts.
  • Analítica Plausible Respeta la privacidad, sin cookies, limpia ante el GDPR. Sin banner de consentimiento; sin sopa de píxeles de rastreo.
  • Build Scripts de Node Un puñado de scripts de Node para sincronizar los includes, estampar hreflang, revisar paridad de locales, renderizar imágenes OG, el flujo de audio y enriquecer el SEO.
Tokens de diseño

Una paleta,
una escala tipográfica, una curva de movimiento.

Cada color, cada espacio, cada radio y cada transición del sitio resuelve a una propiedad personalizada de CSS definida una sola vez en :root. Cambiar el teal principal lo cambia en todos lados — header, footer, enlaces, anillos de foco, estados hover de botones — en una sola línea.

Paleta

  • --teal#1F4E5B
  • --teal-dark#143640
  • --teal-tint#E8F1F3
  • --rust#B8541A
  • --ink#14161A
  • --ink-soft#2A2D33
  • --stone#6B6B6B
  • --cream#FAF7F2
  • --cream-2#F3EEE3

Tipografía

  • Fraunces--font-displayDisplay editorial — H1, texto destacado
  • Inter en sans-serif para texto--font-bodyCuerpo del artículo, chrome de UI, formularios

Radio · Movimiento

  • --r-sm8px
  • --r-md14px
  • --r-lg22px
  • --t-fast180ms
  • --t-med420ms
  • --ease-outcubic-bezier(.16,1,.3,1)
Internacionalización

Bilingüe,
sin el sobrecosto.

Cada página sale al aire con versión en inglés y en español desde el primer día — sin servicio de traducción de terceros, sin cambio de idioma en tiempo de ejecución, sin framework de JS. La versión en español vive bajo /es/ y Google la indexa con prioridad completa.

  1. Un partial por idioma

    El nav y el footer viven en _includes/nav.html + _includes/footer.html (inglés) y _includes/es/ (español). Un paso de build los estampa en cada página, así una sola edición se propaga a 85 archivos.

  2. Hreflang, automático

    scripts/stamp-hreflang.mjs recorre cada página, detecta su contraparte en el otro idioma, e inyecta el par exacto de <link rel="alternate"> que Google espera. Sin papeleo manual.

  3. Strings de UI en tiempo de ejecución

    Las cadenas emitidas por JS (aria-labels, reproductor de audio, validación de formularios) viven en _includes/i18n.es.json y se estampan dentro del <script> de cada página en español, para que site.js las lea como window.__i18n sin una petición de red.

  4. Paridad obligada en el build

    scripts/check-locale-parity.mjs --check sale con código de error si alguna página en inglés no tiene su contraparte en español. Publicar una página sin traducir exige un override explícito — lo normal es "se mantienen en sincronía".

Hojas del operador

Treinta y un papeles
imprimibles.

Cada hoja en /sheets/<slug>/ es un formulario interactivo de una sola página: la llenas, la matemática corre en tu navegador, la imprimes o la guardas como CSV. Cinco paquetes (Operaciones y Márgenes, SEO local y Descubrimiento, Conversiones y Reservas, Marca y Diseño, Confianza y Reseñas) en paridad EN+ES. El pipeline está dirigido por datos desde una sola fuente de verdad, así que agregar una hoja cuesta una entrada de JSON y un fragmento HTML.

  1. Una sola fuente de verdad

    data/sheets.json declara cada hoja — título, resumen, walkaway, cadencia, formato, paquete, pairsWith.{tools,glossary,blog}, status. data/sheets.es.json lleva la prosa larga en español (bullets de when_to_use + mistakes). Un campo cambia una hoja de queued a live, y el build la recoge en todos lados.

  2. Composición shell + fragment

    _includes/sheet-shell.html es la chrome compartida: hero, breadcrumb, mm-card, panel de resultados, fila de mm-actions, panel mm-save, knit de pairs-with. scripts/sheets-fragments/<slug>.html es el cuerpo por hoja — fieldsets, inputs, tablas repetidas, el script de recalc específico. scripts/build-sheet-pages.mjs envuelve la shell alrededor del fragment y escribe 62 páginas (31 hojas × EN + ES).

  3. Bandas centralizadas

    data/benchmarks.json declara cada banda de umbral ("costo primo 55–65% saludable") con mensajes EN + ES y cita de fuente. scripts/build-sheet-benchmarks.mjs genera assets/js/sheet-benchmarks.gen.js, que expone window.Bench.evaluate(metric, value). Actualiza un umbral aquí y 31 hojas siguen.

  4. Payloads de guardado versionados

    Los guardados en el Taller llevan { v: 1, slug, savedAt, inputs, outputs }. El endpoint /api/workbench/sheet-history lee los guardados previos del usuario para la misma hoja y dibuja una sparkline encima del panel de guardar — extractSheetMetric maneja el fallback v-absent para que los guardados anteriores al versionado sigan dibujándose limpios.

  5. Cross-links recíprocos

    32 páginas de glosario estampan un sidecar "Usa esta hoja"; 10 páginas pillar de tema estampan un rail de hojas; 30 páginas de detalle de herramienta estampan un rail "Empareja con papeleo" hacia las hojas que las usan. Cuatro posts del blog enlazan vía post-end-CTA hacia una hoja en el momento de intención. Cada enlace se indexa al revés desde el campo pairsWith de data/sheets.json — sin registro separado que se desfase.

  6. Postura de privacidad verificable

    scripts/check-sheet-no-fetch.mjs rastrea cada fragmento y cada bloque <main> renderizado en busca de patrones de red prohibidos (fetch, XHR, sendBeacon, localStorage en fragmentos, eval, src de script externo). CI falla si algún fragmento contacta cualquier URL. La promesa "se queda en tu navegador" en cada hoja enlaza a /es/receipts/ donde el rastro de auditoría está visible.

Historial de URLs

Tres reestructuraciones,
cero enlaces rotos.

La estructura de URLs de este sitio se ha reorganizado tres veces desde el lanzamiento. Cada enlace entrante desde cada ruta histórica sigue cayendo en la página correcta, en el primer salto, con el equity de SEO intacto. Todo está en _redirects.

  1. Sprint 1 /audit/ /tools/

    Origen. La herramienta de diagnóstico vivía en su propia ruta, dedicada solo a eso.

  2. Primera reestructuración /tools/restaurant-audit/ /tools/audits/restaurant/

    Abrió espacio para más tipos de diagnóstico sin tener que renombrar la herramienta dos veces.

  3. Hoy /tools/audits/restaurant/

    La categoría /tools/ existe para que nuevas herramientas — generadores, convertidores, analizadores — puedan lanzarse sin otra reestructuración.

  4. Sprint K /tools/audits/wellness/ /tools/audits/restaurant/

    El diagnóstico de wellness y su checklist se retiraron cuando el estudio se enfocó primero en restaurantes. En lugar de un 404, los enlaces entrantes caen en la herramienta actual.

Narración en audio

Cada artículo,
leído en voz alta en seis idiomas.

Cada artículo del blog sale al aire con narración en inglés, español, francés, italiano, portugués y chino. El flujo es un script de Node; el reproductor es JS puro. Sin servicio de streaming, sin muro de pago.

Cómo se publica

  • scripts/render-post-audio.mjs corta el artículo por encabezados, genera cada fragmento con text-to-speech y cose el audio.mp3 dentro del propio directorio del artículo.
  • Los atributos data-audio-alt por párrafo les dan a las figuras complejas (gráficas, tablas) una lectura alternativa pensada para el narrador.
  • El schema JSON-LD AudioObject le anuncia la narración a los buscadores para ser elegible como contenido speakable.
  • El reproductor en sí es un solo elemento acoplado en pantalla que solo se renderiza cuando el lector decide activarlo, así el peso por defecto de la página se mantiene honesto.
Búsqueda

Índice estático,
resultados al instante.

La biblioteca se puede buscar completa sin servidor. Presiona K (o Ctrl K, o la tecla /) desde cualquier parte del sitio para probarla.

Cómo se publica

  • Pagefind corre una sola vez al final de cada deploy, lee cada HTML construido en dist/ y emite un índice estático a dist/pagefind/. Sin servidor, sin base de datos, sin API key.
  • El índice se divide automáticamente por <html lang>, así un lector en español recibe resultados en español y uno en inglés recibe resultados en inglés — misma caja de búsqueda, idioma correcto.
  • Un archivo de configuración en pagefind.yml excluye la navegación, el footer, las migas de pan y el chrome de overlay del índice, para que los resultados sean contenido de la página, no relleno.
  • La UI es una ventana modal a medida en site.js (sin la UI por defecto de Pagefind), cargada de forma perezosa la primera vez que se abre, así los lectores que nunca buscan no pagan ni un byte por la función.
  • Primero el teclado: las flechas mueven entre resultados, intro abre, escape cierra. El mouse resalta al pasar. El foco regresa al disparador al cerrar.
Investigación

Cada cita externa
tiene un espejo escrito por Muntin.

Cuando un artículo o herramienta de este sitio cita investigación externa, la cita apunta primero a un resumen escrito por Muntin en /es/learn/research/, y después al estudio original. El lector conserva su lugar en la página que estaba leyendo; el resumen se abre al lado en una pestaña nueva.

Cómo se publica

  • Cada URL externa de investigación que cita la biblioteca está mapeada a una página interna de resumen en /learn/research/<slug>/ — en lenguaje claro, con marco de restaurante, con el hallazgo principal, los números clave y un índice inverso de "cómo lo usa Muntin" que muestra cada artículo que se apoya en el estudio.
  • scripts/wire-research-citations.mjs recorre cada artículo publicado del blog, encuentra anclas externas que coinciden con el mapa conocido de citas, e inyecta un enlace interno a "Leer el resumen de Don" (pestaña nueva) inmediatamente antes de cada una. Es idempotente — re-ejecutarlo no hace nada.
  • Los enlaces a notas de investigación se abren en una pestaña nueva (target="_blank") para que un lector a medio diagnóstico o a medio artículo no pierda su lugar. Las migas de pan en la nota muestran claramente "Inicio › Aprende › Investigación › <nota>" así el lector sabe dónde está.
  • Cada nota de investigación incluye schema JSON-LD completo (ArticlecitationScholarlyArticle) para que los buscadores entiendan la relación resumen-cita-fuente.
  • Hacer clic en cualquier enlace a una nota de investigación ahora abre un cajón inline sobre la página de origen — lateral en escritorio, desde abajo en móvil — así el lector puede ver el resumen, los hallazgos clave y ambos CTA sin salir de su diagnóstico o abandonar el artículo a media lectura. El cajón carga la nota de forma perezosa y extrae el preview con DOMParser, con caché por slug para que la segunda apertura sea instantánea. Si <dialog>.showModal o fetch no están disponibles — o el lector presiona ⌘/Ctrl/shift para forzar una pestaña nueva — el target="_blank" original del enlace toma el relevo. Cero regresión, mejora progresiva completa.
Accesibilidad

Los fundamentos
silenciosos.

La accesibilidad no es una sección del sitio — es un valor por defecto que respeta el sitio entero. Así se ve en la práctica.

  • Enlaces de salto

    Cada página abre con <a class="skip-link" href="#main"> para que quien navega con teclado se salte el nav en cada carga.

  • Landmarks semánticos

    El nav principal, el menú móvil y el selector de idioma cargan cada uno un aria-label distinto. Cada sección se refiere a su encabezado vía aria-labelledby.

  • Foco visible

    Un token dedicado --ring-focus pinta un anillo teal de 3 px sobre cada elemento enfocable. Nunca se quita, nunca se reemplaza por outline: none.

  • Movimiento reducido

    Los reveals al hacer scroll, el pulso del callout de audio y las transiciones del reproductor respetan prefers-reduced-motion y se acortan a casi cero cuando está activo.

  • Contraste de color

    El texto sobre cream pasa WCAG AA con 16.4:1; el teal de los enlaces pasa AA con 4.8:1; el anillo de foco pasa AA Large sobre cada fondo de la paleta.

  • Todo con teclado

    El menú móvil, el menú para compartir, el reproductor de audio, el selector de idioma y cada formulario son totalmente operables solo con teclado. aria-expanded y aria-haspopup reflejan el estado real.

Y si quieres conversarlo

¿Quieres un sitio construido
como este?

Si tienes un negocio pequeño y quieres este mismo cuidado aplicado a tu sitio, agenda una llamada de veinte minutos. Si eres colega del oficio y quieres comparar notas — el correo también funciona.