diff --git a/.github/workflows/site-validation.yml b/.github/workflows/site-validation.yml index d446a4f..9d4535c 100644 --- a/.github/workflows/site-validation.yml +++ b/.github/workflows/site-validation.yml @@ -38,7 +38,6 @@ jobs: node --check tests/consent-coc.cjs - name: Check consent persistence and Code of Conduct version run: | - node --test tests/consent-coc.cjs - node --test tests/voluntariado-page.cjs + node --test tests/*.cjs - name: Check whitespace errors run: git diff --check HEAD^ HEAD diff --git a/2026/agenda.html b/2026/agenda.html index e6fcb31..86b8410 100644 --- a/2026/agenda.html +++ b/2026/agenda.html @@ -5,14 +5,14 @@ Agenda - PyCon Panamá 2026 - + - + @@ -75,7 +75,8 @@

Agenda de PyCon Panamá 2026

-

La agenda estará disponible el lunes 28 de septiembre de 2026.

+

La agenda estará disponible el .

+

La hora de publicación está por confirmar.

@@ -96,6 +97,7 @@

Agenda de PyCon Panamá 2026

+ diff --git a/2026/css/style.css b/2026/css/style.css index af2a2b7..04a8593 100644 --- a/2026/css/style.css +++ b/2026/css/style.css @@ -1187,3 +1187,11 @@ a.sponsor-card:focus-visible { .sponsor-empty > * + * { margin-top: var(--space-3); } + +/* Reserva dos líneas en móvil para todos los estados de la cuenta regresiva. */ +.agenda-countdown { + min-height: 3em; + line-height: 1.5; + font-size: 0.95rem; + font-variant-numeric: tabular-nums; +} diff --git a/2026/js/agenda-countdown.js b/2026/js/agenda-countdown.js new file mode 100644 index 0000000..eacc22e --- /dev/null +++ b/2026/js/agenda-countdown.js @@ -0,0 +1,40 @@ +/** Días calendario en Panamá; no representa una hora de publicación. */ +(function () { + 'use strict'; + + const panamaDate = new Intl.DateTimeFormat('en-US', { + timeZone: 'America/Panama', year: 'numeric', month: '2-digit', day: '2-digit' + }); + + function countdownMessage(publicationDate, now = new Date()) { + const [year, month, day] = publicationDate.split('-').map(Number); + const parts = Object.fromEntries(panamaDate.formatToParts(now).map(part => [part.type, part.value])); + // UTC sirve solo para restar fechas civiles, no fija una hora del evento. + const today = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day)); + const remaining = (Date.UTC(year, month - 1, day) - today) / 86400000; + if (remaining > 1) return `Faltan ${remaining} días para publicar la agenda`; + if (remaining === 1) return 'Falta 1 día para publicar la agenda'; + if (remaining === 0) return 'La agenda se publica hoy'; + return 'Agenda pendiente de publicación'; + } + + if (typeof module !== 'undefined' && module.exports) { + module.exports = { countdownMessage }; + } + + if (typeof document === 'undefined') return; + const countdown = document.getElementById('agenda-countdown'); + const date = document.getElementById('agenda-publication-date'); + if (!countdown || !date) return; + + function update() { + const message = countdownMessage(date.getAttribute('datetime')); + if (countdown.textContent !== message) countdown.textContent = message; + } + update(); + // Cubre pestañas abiertas durante el cambio de día, sin anuncios en vivo. + window.setInterval(update, 60000); + document.addEventListener('visibilitychange', function () { + if (!document.hidden) update(); + }); +})(); diff --git a/README.md b/README.md index 82aa4d5..9358c17 100644 --- a/README.md +++ b/README.md @@ -139,9 +139,9 @@ Para consultas sobre PyCon Panamá, escribe a [pyconpanama@gmail.com](mailto:pyc ## Publicación de la agenda 2026 — issue #118 -Última actualización: 8 de septiembre de 2026. +Última actualización: 27 de septiembre de 2026. -La agenda permanece en espera hasta el lunes 28 de septiembre de 2026. No hay +La agenda permanece en espera hasta el viernes 2 de octubre de 2026. No hay hora definida ni publicación automática: organización debe confirmar el contenido aprobado y el momento de publicación. @@ -158,6 +158,8 @@ Para preparar la restitución: horarios y ponentes por la programación aprobada por organización. 2. Editar la página actual conservando su navegación y metadatos vigentes. Actualizar la descripción y el contenido principal con la agenda aprobada. + Retirar el aviso de publicación, el elemento `agenda-countdown` y la carga + de `js/agenda-countdown.js` al publicar la programación definitiva. 3. Restituir el enlace Agenda en los menús de todas las páginas de `2026/`, incluido `voluntariado/index.html` (ruta `../agenda.html`); la tarjeta de inicio; y los botones de `about.html` y `sedes.html`. Consultar el diff del @@ -169,7 +171,7 @@ Para preparar la restitución: `git diff --check`. Revisar el preview en móvil y escritorio, el menú, los enlaces restituidos y el acceso directo a `/2026/agenda.html`. 6. Integrar el PR revisado cuando organización autorice la publicación el - 28 de septiembre. Verificar el despliegue de Netlify y repetir la navegación + 2 de octubre de 2026. Verificar el despliegue de Netlify y repetir la navegación y el acceso directo en producción antes de dar la restitución por terminada. El aviso se mantiene si falta la aprobación; no se elimina automáticamente por diff --git a/tests/agenda-countdown.cjs b/tests/agenda-countdown.cjs new file mode 100644 index 0000000..5b30ff0 --- /dev/null +++ b/tests/agenda-countdown.cjs @@ -0,0 +1,58 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const vm = require('node:vm'); +const { countdownMessage } = require('../2026/js/agenda-countdown.js'); +const html = fs.readFileSync('2026/agenda.html', 'utf8'); +const publicationDate = html.match(/id="agenda-publication-date" datetime="([^"]+)"/)[1]; + +for (const [instant, expected] of [ + ['2026-09-27T17:00:00Z', 'Faltan 5 días para publicar la agenda'], + ['2026-10-01T04:59:59Z', 'Faltan 2 días para publicar la agenda'], + ['2026-10-01T05:00:00Z', 'Falta 1 día para publicar la agenda'], + ['2026-10-02T04:59:59Z', 'Falta 1 día para publicar la agenda'], + ['2026-10-02T05:00:00Z', 'La agenda se publica hoy'], + ['2026-10-03T04:59:59Z', 'La agenda se publica hoy'], + ['2026-10-03T05:00:00Z', 'Agenda pendiente de publicación'], + ['2027-01-01T12:00:00Z', 'Agenda pendiente de publicación'] +]) test(`agenda: ${instant} en Panamá`, () => { + assert.equal(countdownMessage(publicationDate, new Date(instant)), expected); +}); + +test('resta fechas calendario, incluso entre meses, sin redondear horas', () => { + assert.equal(countdownMessage('2028-03-01', new Date('2028-02-29T23:59:00-05:00')), 'Falta 1 día para publicar la agenda'); +}); + +test('la fecha sigue visible sin JavaScript y no hay anuncios en vivo', () => { + assert.match(html, /datetime="2026-10-02">viernes 2 de octubre de 2026<\/time>/); + assert.match(html, /id="agenda-countdown"[^>]*aria-live="off">La hora de publicación está por confirmar\./); +}); + +test('actualiza al cambiar el día y al volver a la pestaña, sin reescribir el mismo texto', () => { + let instant = '2026-10-02T04:59:59Z'; + let value = ''; + let writes = 0; + let interval; + let onVisibility; + const counter = { get textContent() { return value; }, set textContent(next) { value = next; writes++; } }; + const context = { + Intl, + Date: class extends Date { constructor() { super(instant); } }, + document: { + hidden: false, + getElementById: id => id === 'agenda-countdown' ? counter : { getAttribute: () => publicationDate }, + addEventListener: (event, callback) => { assert.equal(event, 'visibilitychange'); onVisibility = callback; } + }, + window: { setInterval: (callback, delay) => { assert.equal(delay, 60000); interval = callback; } } + }; + vm.runInNewContext(fs.readFileSync('2026/js/agenda-countdown.js', 'utf8'), context); + assert.equal(value, 'Falta 1 día para publicar la agenda'); + interval(); + assert.equal(writes, 1); + instant = '2026-10-02T05:00:00Z'; + interval(); + assert.equal(value, 'La agenda se publica hoy'); + instant = '2026-10-03T05:00:00Z'; + onVisibility(); + assert.equal(value, 'Agenda pendiente de publicación'); +});