From 1fe52436a3ebbbb4b48081da9eaba3c9b90b4771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesc=20Arp=C3=AD=20Roca?= Date: Sat, 13 Jun 2026 10:42:46 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20agregar=20secci=C3=B3n=20de=20orado?= =?UTF-8?q?res=20plenarios=20en=20la=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nueva sección en la home entre SectionMain y SectionEarlyBird que muestra los oradores plenarios como tarjetas en un grid responsive. Cada orador se define como un .md en src/data/speakers/ con frontmatter tipado (nombre, orden, foto, links a github/linkedin/instagram/website, descripción). Se incluye ada-lovelace.md.template como punto de partida para futuros oradores. La sección no se renderiza si no hay oradores cargados. La descripción usa texto plano multilínea renderizado con whitespace-pre-line. i18n completo en es/en/ca con keys speakers.* y aria-labels apropiados para los links externos (cumplen regla de new_tab). --- public/speakers/ada-lovelace.webp | Bin 0 -> 3210 bytes src/components/home/SectionSpeakers.astro | 37 ++++++++++ src/components/home/SpeakerCard.astro | 80 +++++++++++++++++++++ src/components/index.astro | 2 + src/data/speakers/ada-lovelace.md.template | 13 ++++ src/i18n/home.ts | 24 +++++++ src/types/speakers.ts | 14 ++++ 7 files changed, 170 insertions(+) create mode 100644 public/speakers/ada-lovelace.webp create mode 100644 src/components/home/SectionSpeakers.astro create mode 100644 src/components/home/SpeakerCard.astro create mode 100644 src/data/speakers/ada-lovelace.md.template create mode 100644 src/types/speakers.ts diff --git a/public/speakers/ada-lovelace.webp b/public/speakers/ada-lovelace.webp new file mode 100644 index 0000000000000000000000000000000000000000..17b55e1ba565d4ef0526cf93791d81d8484e151e GIT binary patch literal 3210 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 literal 0 HcmV?d00001 diff --git a/src/components/home/SectionSpeakers.astro b/src/components/home/SectionSpeakers.astro new file mode 100644 index 0000000..572ec54 --- /dev/null +++ b/src/components/home/SectionSpeakers.astro @@ -0,0 +1,37 @@ +--- +import { texts } from '@/i18n/home' +import SectionTitle from '../SectionTitle.astro' +import CenteredPanel from '../CenteredPanel.astro' +import type { ISpeaker } from '../../types/speakers' +import SpeakerCard from './SpeakerCard.astro' + +const speakers = Object.values(import.meta.glob('../../data/speakers/*.md', { eager: true })) as { + frontmatter: ISpeaker +}[] + +interface Props { + lang: string +} + +const { lang } = Astro.props +const t = texts[lang as keyof typeof texts] + +const sortedSpeakers = speakers.map((s) => s.frontmatter).sort((a, b) => a.order - b.order) +--- + +{ + sortedSpeakers.length > 0 && ( +
+ + + +
    + {sortedSpeakers.map((speaker) => ( +
  • + +
  • + ))} +
+
+ ) +} diff --git a/src/components/home/SpeakerCard.astro b/src/components/home/SpeakerCard.astro new file mode 100644 index 0000000..d9da96d --- /dev/null +++ b/src/components/home/SpeakerCard.astro @@ -0,0 +1,80 @@ +--- +import { Globe } from '@lucide/astro' +import type { ISpeaker, ISpeakerLink } from '../../types/speakers' +import { texts } from '../../i18n/home' +import { menuTexts } from '../../i18n/menu' +import SocialIcon from '../icons/SocialIcon.astro' + +interface Props { + speaker: ISpeaker + lang: string +} + +const { speaker, lang } = Astro.props +const t = texts[lang as keyof typeof texts] +const menuT = menuTexts[lang as keyof typeof menuTexts] + +const altPhoto = t['speakers.altphoto'].replace('{name}', speaker.name) + +function ariaForLink(link: ISpeakerLink, name: string): string { + const key = `speakers.aria_${link.type}` as + | 'speakers.aria_github' + | 'speakers.aria_linkedin' + | 'speakers.aria_instagram' + | 'speakers.aria_website' + const label = t[key] ?? link.type + return `${label.replace('{name}', name)} ${menuT.new_tab}` +} + +const linkClasses = + 'inline-flex items-center justify-center w-9 h-9 rounded-full bg-white/[0.03] border border-white/[0.06] hover:bg-white/[0.08] hover:border-white/[0.15] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pycon-yellow transition-colors' +--- + +
+ {altPhoto} + +

{speaker.name}

+ +
    + { + speaker.links.map((link) => ( +
  • + {link.type === 'website' ? ( + + + ) : ( + + + )} +
  • + )) + } +
+ +

+ {speaker.description} +

+
diff --git a/src/components/index.astro b/src/components/index.astro index 6f88561..79d7b6c 100644 --- a/src/components/index.astro +++ b/src/components/index.astro @@ -1,6 +1,7 @@ --- import Layout from '@/layouts/Layout.astro' import SectionMain from './home/SectionMain.astro' +import SectionSpeakers from './home/SectionSpeakers.astro' import SectionEarlyBird from './home/SectionEarlyBird.astro' import SectionSponsors from './home/SectionSponsors.astro' @@ -14,6 +15,7 @@ const { lang } = Astro.props
+
diff --git a/src/data/speakers/ada-lovelace.md.template b/src/data/speakers/ada-lovelace.md.template new file mode 100644 index 0000000..7f5fe8c --- /dev/null +++ b/src/data/speakers/ada-lovelace.md.template @@ -0,0 +1,13 @@ +--- +name: 'Ada Lovelace' +order: 1 +photo: '/speakers/ada-lovelace.webp' +links: + - type: 'github' + url: 'https://github.com/ada-lovelace' + - type: 'linkedin' + url: 'https://linkedin.com/in/ada-lovelace' + - type: 'website' + url: 'https://ada-lovelace.dev' +description: Pionera de la computación y primera programadora de la historia. Trabajó con Charles Babbage en la Máquina Analítica y escribió lo que se considera el primer algoritmo destinado a ser procesado por una máquina. Su visión de las computadoras como herramientas capaces de manipular símbolos, música y texto anticipó por más de un siglo los usos modernos de la informática. +--- diff --git a/src/i18n/home.ts b/src/i18n/home.ts index 3d31e07..8e39146 100644 --- a/src/i18n/home.ts +++ b/src/i18n/home.ts @@ -23,6 +23,14 @@ export const texts = { 'earlybird.description': 'La venta de entradas con descuento Early Bird ya ha comenzado. ¡No te pierdas esta oportunidad única de aprender, conectar y crecer en la comunidad Python!', 'earlybird.button': 'Comprar entradas', + 'speakers.title': 'Oradores plenarios', + 'speakers.description': + 'Las voces más relevantes de la comunidad Python compartirán su visión y experiencia en la PyConES 2026. Descubre a las personas que protagonizarán las charlas plenarias de esta edición.', + 'speakers.altphoto': 'Foto de {name}', + 'speakers.aria_github': 'GitHub de {name}', + 'speakers.aria_linkedin': 'LinkedIn de {name}', + 'speakers.aria_instagram': 'Instagram de {name}', + 'speakers.aria_website': 'Sitio web de {name}', }, en: { 'index.initializing': 'Initialising system...', @@ -49,6 +57,14 @@ export const texts = { 'earlybird.description': 'Early Bird discounted tickets are now on sale. Do not miss this unique opportunity to learn, connect, and grow in the Python community!', 'earlybird.button': 'Buy tickets', + 'speakers.title': 'Plenary Speakers', + 'speakers.description': + 'The most relevant voices in the Python community will share their vision and experience at PyConES 2026. Meet the people headlining the plenary talks of this edition.', + 'speakers.altphoto': 'Photo of {name}', + 'speakers.aria_github': '{name} on GitHub', + 'speakers.aria_linkedin': '{name} on LinkedIn', + 'speakers.aria_instagram': '{name} on Instagram', + 'speakers.aria_website': "{name}'s website", }, ca: { 'index.initializing': 'Inicialitzant sistema...', @@ -75,5 +91,13 @@ export const texts = { 'earlybird.description': "La venda d'entrades amb descompte Early Bird ja ha començat. No et perdis aquesta oportunitat única d'aprendre, connectar i créixer en la comunitat Python!", 'earlybird.button': 'Comprar entrades', + 'speakers.title': 'Ponents plenaris', + 'speakers.description': + 'Les veus més rellevants de la comunitat Python compartiran la seva visió i experiència a la PyConES 2026. Descobreix les persones que protagonitzaran les xerrades plenàries d’aquesta edició.', + 'speakers.altphoto': 'Foto de {name}', + 'speakers.aria_github': 'GitHub de {name}', + 'speakers.aria_linkedin': 'LinkedIn de {name}', + 'speakers.aria_instagram': 'Instagram de {name}', + 'speakers.aria_website': 'Lloc web de {name}', }, } as const diff --git a/src/types/speakers.ts b/src/types/speakers.ts new file mode 100644 index 0000000..f488ed4 --- /dev/null +++ b/src/types/speakers.ts @@ -0,0 +1,14 @@ +export type TSpeakerLinkType = 'github' | 'linkedin' | 'instagram' | 'website' + +export interface ISpeakerLink { + type: TSpeakerLinkType + url: string +} + +export interface ISpeaker { + name: string + order: number + photo: string + links: ISpeakerLink[] + description: string +} From 097840bb3d36c74515c889a1348a46341d2db317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesc=20Arp=C3=AD=20Roca?= Date: Mon, 15 Jun 2026 16:32:06 +0200 Subject: [PATCH 2/2] fix(a11y): address review feedback on speaker section - Remove redundant role='list' from