Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/speakers/ada-lovelace.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/components/home/SectionSpeakers.astro
Original file line number Diff line number Diff line change
@@ -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 && (
<div class="flex flex-col items-center gap-6">
<SectionTitle title={t['speakers.title']} />
<CenteredPanel text={t['speakers.description']} />

<ul class="w-full grid gap-8 md:grid-cols-2 lg:grid-cols-3 list-none m-0 p-0 mt-4">
{sortedSpeakers.map((speaker) => (
<li>
<SpeakerCard speaker={speaker} lang={lang} />
</li>
))}
</ul>
Comment thread
francescarpi marked this conversation as resolved.
</div>
)
}
83 changes: 83 additions & 0 deletions src/components/home/SpeakerCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
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'
---

<article
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"
>
<img
src={speaker.photo}
alt={altPhoto}
width="160"
height="160"
loading="lazy"
class="rounded-full w-40 h-40 object-cover border-2 border-pycon-orange/30 mb-6"
/>

<h3 class="text-xl font-bold text-pycon-orange mb-4">{speaker.name}</h3>

<ul
aria-label={t['speakers.aria_social_links'].replace('{name}', speaker.name)}
class="flex items-center gap-3 list-none m-0 p-0 mb-4"
>
{
speaker.links.map((link) => (
<li>
{link.type === 'website' ? (
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
aria-label={ariaForLink(link, speaker.name)}
class={linkClasses}
>
<Globe class="w-5 h-5" aria-hidden="true" focusable="false" />
</a>
) : (
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
aria-label={ariaForLink(link, speaker.name)}
class={linkClasses}
>
<SocialIcon platform={link.type} size="md" aria-hidden="true" />
</a>
)}
</li>
))
}
</ul>

<p class="text-sm leading-relaxed text-pycon-gray-25 whitespace-pre-line text-justify">
{speaker.description}
</p>
</article>
8 changes: 5 additions & 3 deletions src/components/icons/Icon.astro
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ if (!LucideIconComponent && !fallback) {
<LucideIconComponent
class={classes}
style={style}
aria-label={ariaLabel}
aria-label={ariaHidden ? undefined : ariaLabel}
aria-hidden={ariaHidden ? 'true' : undefined}
focusable="false"
{...rest}
/>
) : fallback ? (
<span
class={`${sizeClasses} ${className} inline-flex items-center justify-center`}
style={style}
aria-label={ariaLabel}
aria-label={ariaHidden ? undefined : ariaLabel}
aria-hidden={ariaHidden ? 'true' : undefined}
{...rest}
>
Expand All @@ -63,8 +64,9 @@ if (!LucideIconComponent && !fallback) {
<CircleAlert
class={classes}
style={style}
aria-label={ariaLabel || `Unknown icon: ${name}`}
aria-label={ariaHidden ? undefined : ariaLabel || `Unknown icon: ${name}`}
aria-hidden={ariaHidden ? 'true' : undefined}
focusable="false"
{...rest}
/>
)
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.astro
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -14,6 +15,7 @@ const { lang } = Astro.props
<Layout title="PyConES 2026">
<div class="flex flex-col gap-20">
<SectionMain lang={lang} />
<SectionSpeakers lang={lang} />
<SectionEarlyBird lang={lang} />
<SectionSponsors lang={lang} />
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/data/speakers/ada-lovelace.md.template
Original file line number Diff line number Diff line change
@@ -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.
---
27 changes: 27 additions & 0 deletions src/i18n/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ 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}',
'speakers.aria_social_links': 'Enlaces sociales de {name}',
},
en: {
'index.initializing': 'Initialising system...',
Expand Down Expand Up @@ -51,6 +60,15 @@ 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",
'speakers.aria_social_links': "{name}'s social links",
},
ca: {
'index.initializing': 'Inicialitzant sistema...',
Expand Down Expand Up @@ -78,5 +96,14 @@ 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}',
'speakers.aria_social_links': 'Enllaços socials de {name}',
},
} as const
14 changes: 14 additions & 0 deletions src/types/speakers.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading