Skip to content

Commit 1fe5243

Browse files
committed
feat: agregar sección de oradores plenarios en la home
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).
1 parent 6dfd253 commit 1fe5243

7 files changed

Lines changed: 170 additions & 0 deletions

File tree

public/speakers/ada-lovelace.webp

3.13 KB
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
import { texts } from '@/i18n/home'
3+
import SectionTitle from '../SectionTitle.astro'
4+
import CenteredPanel from '../CenteredPanel.astro'
5+
import type { ISpeaker } from '../../types/speakers'
6+
import SpeakerCard from './SpeakerCard.astro'
7+
8+
const speakers = Object.values(import.meta.glob('../../data/speakers/*.md', { eager: true })) as {
9+
frontmatter: ISpeaker
10+
}[]
11+
12+
interface Props {
13+
lang: string
14+
}
15+
16+
const { lang } = Astro.props
17+
const t = texts[lang as keyof typeof texts]
18+
19+
const sortedSpeakers = speakers.map((s) => s.frontmatter).sort((a, b) => a.order - b.order)
20+
---
21+
22+
{
23+
sortedSpeakers.length > 0 && (
24+
<div class="flex flex-col items-center gap-6">
25+
<SectionTitle title={t['speakers.title']} />
26+
<CenteredPanel text={t['speakers.description']} />
27+
28+
<ul role="list" class="w-full grid gap-8 md:grid-cols-2 lg:grid-cols-3 list-none m-0 p-0 mt-4">
29+
{sortedSpeakers.map((speaker) => (
30+
<li>
31+
<SpeakerCard speaker={speaker} lang={lang} />
32+
</li>
33+
))}
34+
</ul>
35+
</div>
36+
)
37+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
import { Globe } from '@lucide/astro'
3+
import type { ISpeaker, ISpeakerLink } from '../../types/speakers'
4+
import { texts } from '../../i18n/home'
5+
import { menuTexts } from '../../i18n/menu'
6+
import SocialIcon from '../icons/SocialIcon.astro'
7+
8+
interface Props {
9+
speaker: ISpeaker
10+
lang: string
11+
}
12+
13+
const { speaker, lang } = Astro.props
14+
const t = texts[lang as keyof typeof texts]
15+
const menuT = menuTexts[lang as keyof typeof menuTexts]
16+
17+
const altPhoto = t['speakers.altphoto'].replace('{name}', speaker.name)
18+
19+
function ariaForLink(link: ISpeakerLink, name: string): string {
20+
const key = `speakers.aria_${link.type}` as
21+
| 'speakers.aria_github'
22+
| 'speakers.aria_linkedin'
23+
| 'speakers.aria_instagram'
24+
| 'speakers.aria_website'
25+
const label = t[key] ?? link.type
26+
return `${label.replace('{name}', name)} ${menuT.new_tab}`
27+
}
28+
29+
const linkClasses =
30+
'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'
31+
---
32+
33+
<article
34+
class="h-full bg-pycon-black/40 p-8 rounded-2xl border border-white/5 hover:border-pycon-orange/50 transition-all motion-safe:hover:-translate-y-2 flex flex-col items-center text-center"
35+
>
36+
<img
37+
src={speaker.photo}
38+
alt={altPhoto}
39+
width="160"
40+
height="160"
41+
loading="lazy"
42+
class="rounded-full w-40 h-40 object-cover border-2 border-pycon-orange/30 mb-6"
43+
/>
44+
45+
<h3 class="text-xl font-bold text-pycon-orange mb-4">{speaker.name}</h3>
46+
47+
<ul role="list" class="flex items-center gap-3 list-none m-0 p-0 mb-4">
48+
{
49+
speaker.links.map((link) => (
50+
<li>
51+
{link.type === 'website' ? (
52+
<a
53+
href={link.url}
54+
target="_blank"
55+
rel="noopener noreferrer"
56+
aria-label={ariaForLink(link, speaker.name)}
57+
class={linkClasses}
58+
>
59+
<Globe class="w-5 h-5" aria-hidden="true" />
60+
</a>
61+
) : (
62+
<a
63+
href={link.url}
64+
target="_blank"
65+
rel="noopener noreferrer"
66+
aria-label={ariaForLink(link, speaker.name)}
67+
class={linkClasses}
68+
>
69+
<SocialIcon platform={link.type} size="md" aria-hidden="true" />
70+
</a>
71+
)}
72+
</li>
73+
))
74+
}
75+
</ul>
76+
77+
<p class="text-sm leading-relaxed text-pycon-gray-25 whitespace-pre-line text-justify">
78+
{speaker.description}
79+
</p>
80+
</article>

src/components/index.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import Layout from '@/layouts/Layout.astro'
33
import SectionMain from './home/SectionMain.astro'
4+
import SectionSpeakers from './home/SectionSpeakers.astro'
45
import SectionEarlyBird from './home/SectionEarlyBird.astro'
56
import SectionSponsors from './home/SectionSponsors.astro'
67
@@ -14,6 +15,7 @@ const { lang } = Astro.props
1415
<Layout title="PyConES 2026">
1516
<div class="flex flex-col gap-20">
1617
<SectionMain lang={lang} />
18+
<SectionSpeakers lang={lang} />
1719
<SectionEarlyBird lang={lang} />
1820
<SectionSponsors lang={lang} />
1921
</div>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
name: 'Ada Lovelace'
3+
order: 1
4+
photo: '/speakers/ada-lovelace.webp'
5+
links:
6+
- type: 'github'
7+
url: 'https://github.com/ada-lovelace'
8+
- type: 'linkedin'
9+
url: 'https://linkedin.com/in/ada-lovelace'
10+
- type: 'website'
11+
url: 'https://ada-lovelace.dev'
12+
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.
13+
---

src/i18n/home.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export const texts = {
2323
'earlybird.description':
2424
'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!',
2525
'earlybird.button': 'Comprar entradas',
26+
'speakers.title': 'Oradores plenarios',
27+
'speakers.description':
28+
'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.',
29+
'speakers.altphoto': 'Foto de {name}',
30+
'speakers.aria_github': 'GitHub de {name}',
31+
'speakers.aria_linkedin': 'LinkedIn de {name}',
32+
'speakers.aria_instagram': 'Instagram de {name}',
33+
'speakers.aria_website': 'Sitio web de {name}',
2634
},
2735
en: {
2836
'index.initializing': 'Initialising system...',
@@ -49,6 +57,14 @@ export const texts = {
4957
'earlybird.description':
5058
'Early Bird discounted tickets are now on sale. Do not miss this unique opportunity to learn, connect, and grow in the Python community!',
5159
'earlybird.button': 'Buy tickets',
60+
'speakers.title': 'Plenary Speakers',
61+
'speakers.description':
62+
'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.',
63+
'speakers.altphoto': 'Photo of {name}',
64+
'speakers.aria_github': '{name} on GitHub',
65+
'speakers.aria_linkedin': '{name} on LinkedIn',
66+
'speakers.aria_instagram': '{name} on Instagram',
67+
'speakers.aria_website': "{name}'s website",
5268
},
5369
ca: {
5470
'index.initializing': 'Inicialitzant sistema...',
@@ -75,5 +91,13 @@ export const texts = {
7591
'earlybird.description':
7692
"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!",
7793
'earlybird.button': 'Comprar entrades',
94+
'speakers.title': 'Ponents plenaris',
95+
'speakers.description':
96+
'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ó.',
97+
'speakers.altphoto': 'Foto de {name}',
98+
'speakers.aria_github': 'GitHub de {name}',
99+
'speakers.aria_linkedin': 'LinkedIn de {name}',
100+
'speakers.aria_instagram': 'Instagram de {name}',
101+
'speakers.aria_website': 'Lloc web de {name}',
78102
},
79103
} as const

src/types/speakers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type TSpeakerLinkType = 'github' | 'linkedin' | 'instagram' | 'website'
2+
3+
export interface ISpeakerLink {
4+
type: TSpeakerLinkType
5+
url: string
6+
}
7+
8+
export interface ISpeaker {
9+
name: string
10+
order: number
11+
photo: string
12+
links: ISpeakerLink[]
13+
description: string
14+
}

0 commit comments

Comments
 (0)