diff --git a/README.md b/README.md index 5b206dd..1830003 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ En el bloque **Cuenta** configuras lo que se ve en la cabecera del chat: - **Subtítulo** (p. ej. «en línea»); cuando el chat «está escribiendo» se cambia temporalmente por *escribiendo…* en verde. - **Avatar**: súbelo desde un archivo (se convierte a dataURL) o pega una URL/dataURL; también puedes quitarlo (se usa un placeholder SVG). - **Cuenta verificada**: activa o desactiva la insignia azul junto al nombre. -- **Tema**: claro u oscuro (también con el botón *Tema* de la barra superior; se guarda con el resto del estado). + +El **tema** (claro u oscuro) ya no está en Cuenta: se cambia con el botón *Tema* de la barra superior o con la tecla **T**, y se guarda con el resto del estado. ### Añadir, editar y reordenar mensajes @@ -42,7 +43,8 @@ En el bloque **Cuenta** configuras lo que se ve en la cabecera del chat: - Cada mensaje es una **tarjeta colapsable** con un resumen; haz clic para desplegar sus campos. - En cada tarjeta puedes cambiar los campos comunes: **Dirección** (Entrada/Salida), **Hora** (texto literal `HH:MM`, se muestra tal cual) y, solo en los salientes, **Estado (ticks)**: Enviado ✓ / Entregado ✓✓ / Leído ✓✓ (celeste). - Acciones por tarjeta: **↑ / ↓** reordenar, **⧉** duplicar (con IDs regenerados) y **✕** borrar (pide confirmación). -- Todo cambio se guarda al instante y se refleja en el teléfono. El botón **Restablecer** de la barra superior borra `localStorage` y restaura la demo «Café Aurora». +- Todo cambio se guarda al instante y se refleja en el teléfono. En el menú **⋯** de la barra superior, **Volver al ejemplo Café Aurora** restaura la demo de ejemplo (pide confirmación y se puede deshacer). +- Los botones, filas y tarjetas sin URL tienen la casilla **Se pulsa sola en la demo**: durante *Reproducir demo* se pulsa automáticamente (la cabecera plegada muestra el chip «▶ auto»). ### Campos por tipo de mensaje @@ -51,7 +53,7 @@ En el bloque **Cuenta** configuras lo que se ve en la cabecera del chat: | Texto | texto (admite formato WhatsApp: `*negrita*`, `_cursiva_`, `~tachado~`, `` ` ```monoespaciado``` ` ` y URLs clicables) | | Imagen / Vídeo | archivo o URL + caption opcional | | Audio | archivo opcional, casilla **nota de voz** (waveform + micro) y duración (`0:12`) | -| Documento | nombre de archivo con extensión (define el color del icono: pdf, docx, xlsx…), caption y archivo opcional | +| Documento | nombre de archivo con extensión (define el color del icono: pdf, docx, xlsx…), tamaño opcional («243 KB • pdf»), caption y archivo opcional | | Sticker | imagen/WebP (se muestra sin burbuja ni hora) | | Ubicación | latitud, longitud, nombre y dirección (el mapa es un gráfico SVG con pin) | | Contacto | nombre, teléfono, email y empresa | @@ -91,11 +93,17 @@ Si un botón no tiene respuestas, al pulsarlo solo sale la burbuja con su texto. ## Modo demo (columna derecha) -- **Reproducir demo**: vacía el chat y revela los mensajes uno a uno (~600 ms por mensaje, con «escribiendo…» antes de cada mensaje entrante), como si la conversación ocurriera en directo. Pulsar de nuevo (**■ Detener**) lo cancela. +- **Reproducir demo**: vacía el chat y revela los mensajes uno a uno (~600 ms por mensaje, con «escribiendo…» antes de cada mensaje entrante), como si la conversación ocurriera en directo. Mientras corre se ve el contador «Paso N/M»; pulsar de nuevo (**■ Detener**) lo cancela. +- **Velocidad**: el selector junto al botón ajusta la reproducción (0,5x, 1x, 2x). +- **Zoom** (solo escritorio): *Ajustar* calcula el tamaño del teléfono según el hueco; *100 %* y *125 %* lo fijan y el panel hace scroll. Se recuerda en este navegador. +- **Repetir desde el inicio** (antes «Reiniciar chat»): descarta lo pulsado y escrito en el teléfono y vuelve al guion desde el primer mensaje. +- **Nueva demo** (botón de la barra superior o tecla **N**): abre los escenarios de ejemplo y tus demos guardadas. +- **Deshacer / Rehacer**: botones de la barra superior o **Ctrl/⌘ + Z** y **Ctrl/⌘ + Shift + Z**. Cargar un escenario, una demo o el ejemplo deja siempre un punto de deshacer. - **Mensajes libres**: escribe en el campo *Mensaje* del composer y envía (Enter o botón): sale una burbuja con la **hora actual** y ticks automáticos ✓ → ✓✓ → ✓✓ azul. - **Hoja de lista (sheet)**: al pulsar el botón de un mensaje de tipo *Lista* se abre la hoja inferior con las secciones y filas; toca una fila para disparar su flujo, o toca el fondo oscuro para cerrarla. - **Modo presentación**: oculta el editor y deja solo el iPhone centrado, perfecto para pantallazos o para proyectar. Se alterna con el botón *Presentación* de la barra superior. - **Tema**: alterna claro/oscuro en toda la app (editor y chat). +- **Teclado**: el enlace **Saltar a la vista previa** (visible al tabular) y las teclas **G** / **F6** alternan el foco entre editor y teléfono. El diálogo *Atajos* lista el resto. --- diff --git a/SPEC.md b/SPEC.md index ab994a7..3692e1c 100644 --- a/SPEC.md +++ b/SPEC.md @@ -52,7 +52,8 @@ Convenciones: `src`/`avatar`/`headerMedia`/`media` = dataURL o URL remota; `""` de `WAM.svgPlaceholder(texto)`. Botones con `url` no vacía se renderizan como `` (cta_url, template URL, card de carousel); sin `url` → flujo interactivo con `respuestas`. Botón de plantilla o card con URL no permitida: `normalizar` deja `url: ""` y añade `urlBloqueada: true` -(sigue siendo un botón de enlace, inerte; nunca un quick reply con flujo; `resolverFlujo` lo ignora). +(sigue siendo un botón de enlace, inerte `.qr-btn-inactiva` en render.js; nunca un quick reply con flujo; +`resolverFlujo` lo ignora). Editar a mano el campo URL en el editor retira la marca. `WAM.normalizar(o)` rellena campos faltantes por tipo y devuelve el estado válido (import/upgrade). Campo opcional `autoPulsar` (entero ≥ 0) en `template`/`buttons`/`list`/`carousel` (también en respuestas anidadas): índice del botón, de la fila aplanada de la lista o de la card que «Reproducir demo» pulsa sola @@ -112,7 +113,9 @@ cancela demo y flujos (interactivity.js envuelve `WAM.render`). - Documento: campo opcional `tamano` (y `headerTamano` en la cabecera de plantilla) → subtítulo "243 KB • pdf"; sin él, solo la extensión en mayúsculas ("PDF"). - Hooks de CSS (css/iphone.css): `body[data-wam-zoom="100"|"125"]` fija la escala del teléfono (sin - atributo = ajuste automático); en ≤ 520 px el teléfono va sin marco salvo con `body.con-marco`. + atributo = ajuste automático), solo ≥ 901 px y fuera de Presentación. Lo pone `#sel-zoom` (ux.js, + `WAM.setZoom(v)`, persistido en `localStorage["wam-zoom"]`); en ≤ 520 px el teléfono va sin marco salvo + con `body.con-marco`. - `WAM.tick(estado)` → string SVG inline (✓ / ✓✓ del color de la hora; ✓✓ leído azul iOS `#027bfc`) - Botones del teléfono: `"; html += '
' + '' + diff --git a/js/store.js b/js/store.js index be8c5a7..06048fe 100644 --- a/js/store.js +++ b/js/store.js @@ -359,6 +359,8 @@ window.WAM = window.WAM || {}; msg.src = urlLimpia(m.src, "media"); msg.caption = cad(m.caption); msg.filename = cad(m.filename, "documento.pdf") || "documento.pdf"; + /* opcional: «243 KB» para el subtítulo «243 KB • pdf» (render.js) */ + if (cad(m.tamano).trim()) msg.tamano = cad(m.tamano).trim().slice(0, 20); break; case "sticker": msg.src = urlLimpia(m.src, "media"); @@ -380,6 +382,9 @@ window.WAM = window.WAM || {}; msg.headerTipo = HEADER_TIPOS.indexOf(m.headerTipo) !== -1 ? m.headerTipo : ""; msg.headerTexto = cad(m.headerTexto); msg.headerMedia = urlLimpia(m.headerMedia, "media"); + if (msg.headerTipo === "documento" && cad(m.headerTamano).trim()) { + msg.headerTamano = cad(m.headerTamano).trim().slice(0, 20); + } msg.body = cad(m.body); msg.footer = cad(m.footer); msg.botones = botones(m.botones); diff --git a/js/ux.js b/js/ux.js index 5182de6..2b1768a 100644 --- a/js/ux.js +++ b/js/ux.js @@ -955,6 +955,30 @@ temaPintado = tema; } + /* ------------------------------------------------------------------ + Zoom manual del teléfono (Ajustar / 100 % / 125 %). El CSS vive en + iphone.css (body[data-wam-zoom]); sin atributo = ajustar al hueco. + ------------------------------------------------------------------ */ + var CLAVE_ZOOM = "wam-zoom"; + function aplicarZoom(v) { + v = v === "100" || v === "125" ? v : ""; + if (v) document.body.setAttribute("data-wam-zoom", v); + else document.body.removeAttribute("data-wam-zoom"); + var sel = porId("sel-zoom"); + if (sel && sel.value !== v) sel.value = v; + return v; + } + WAM.setZoom = function (v) { + v = aplicarZoom(String(v == null ? "" : v)); + if (v) lsSet(CLAVE_ZOOM, v); else lsDel(CLAVE_ZOOM); + return v; + }; + function enlazarZoom() { + aplicarZoom(lsGet(CLAVE_ZOOM) || ""); + var sel = porId("sel-zoom"); + if (sel) sel.addEventListener("change", function () { WAM.setZoom(sel.value); }); + } + /* ------------------------------------------------------------------ Inicialización (una sola vez) ------------------------------------------------------------------ */ @@ -978,6 +1002,7 @@ observarToolbar(); enlazarChat(); enlazarSaltos(); + enlazarZoom(); envolverAnuncios(); sincronizarTema();