Skip to content

Commit a866dea

Browse files
itziarZGctrl-alt-d
andauthored
fix: fix accessibility (#20)
- H1 "Matrix" Effect: Implemented a separation of concerns. The 'h1' now uses an aria-label with the static text "PyCon ES 2026", while the visual animation is wrapped in a span with aria-hidden="true". Added a check for prefers-reduced-motion. If enabled, the text renders instantly without the glitch effect. - Dynamic Subtitle (Loading Effect): Hidden decorative chars using aria-hidden="true". Added aria-live="polite" and aria-busy logic. The screen reader now waits for the loading animation to finish before announcing the final venue information. The blinking cursor now stops if the user prefers reduced motion (motion-reduce:animate-none). - Sponsor Button: Wrapped brackets in aria-hidden. Added a descriptive aria-label ("Copiar email..."). Updated the copy-to-clipboard logic to provide auditory feedback ("Email copiado exitosamente") via aria-label updates. -Refactor: Extracted the email address to src/consts.ts and passed it via data-attributes to the DOM, avoiding hardcoded strings in the component. --------- Co-authored-by: dani herrera <ctrl.alt.d@gmail.com>
1 parent 172bd16 commit a866dea

2 files changed

Lines changed: 60 additions & 19 deletions

File tree

src/pages/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const SPONSORS_EMAIL = 'sponsors@2026.es.pycon.org';

src/pages/index.astro

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
---
22
import Layout from '../layouts/Layout.astro'
33
import '@fontsource-variable/jetbrains-mono'
4+
import { SPONSORS_EMAIL } from './constants'
45
---
56

67
<Layout title="PyConES 2026">
78
<main class="min-h-screen flex flex-col justify-center items-center text-center px-4">
89
<div class="relative z-10 max-w-3xl">
910
<h1
1011
class="text-4xl md:text-7xl font-bold text-white font-mono tracking-tighter cursor-default"
11-
data-value="PYCON ES 2026"
12+
aria-label="PyCon ES 2026"
1213
>
13-
PYCON ES 2026
14+
<span aria-hidden="true" data-value="PYCON ES 2026" id="matrix-text"> PYCON ES 2026 </span>
1415
</h1>
1516

16-
<h2 class="mt-4 text-xl md:text-2xl text-green-400 font-mono h-8">
17-
&gt; <span id="subtitle">Initialising system...</span><span class="animate-pulse">_</span>
17+
<h2
18+
id="subtitle-container"
19+
class="mt-4 text-xl md:text-2xl text-green-400 font-mono h-8 flex justify-center items-center gap-2"
20+
aria-live="polite"
21+
aria-busy="true"
22+
>
23+
<span aria-hidden="true" class="select-none">&gt;</span>
24+
<span id="subtitle">Initialising system...</span>
25+
<span class="animate-pulse motion-reduce:animate-none" aria-hidden="true">_</span>
1826
</h2>
1927

2028
<div
@@ -23,17 +31,34 @@ import '@fontsource-variable/jetbrains-mono'
2331
>
2432
<button
2533
id="sponsor-btn"
26-
class="group relative px-7 py-2 bg-green-500 text-black font-mono font-bold text-lg hover:bg-green-400 transition-all duration-300 shadow-[0_0_15px_rgba(34,197,94,0.5)] hover:shadow-[0_0_25px_rgba(34,197,94,0.8)] cursor-pointer"
34+
type="button"
35+
data-email={SPONSORS_EMAIL}
36+
aria-describedby="sponsor-hint"
37+
class="group relative px-7 py-2 bg-green-500 text-black font-mono font-bold text-lg hover:bg-green-400 transition-all duration-300 shadow-[0_0_15px_rgba(34,197,94,0.5)] hover:shadow-[0_0_25px_rgba(34,197,94,0.8)] cursor-pointer focus:outline-none focus:ring-4 focus:ring-green-300/50 rounded-sm"
2738
>
2839
<span
2940
class="absolute inset-0 w-full h-full bg-white opacity-0 group-hover:opacity-20 transition-opacity pointer-events-none"
3041
></span>
31-
<span id="sponsor-text">&lt; BECOME A SPONSOR /&gt;</span>
42+
43+
<span id="sponsor-text">
44+
<span aria-hidden="true">&lt; </span lang='en'>BECOME A SPONSOR<span aria-hidden="true"> /&gt;</span>
45+
</span>
46+
<span id="sponsor-hint" class="sr-only">Copia el email de contacto para patrocinadores</span>
3247
</button>
48+
49+
<div
50+
id="copy-confirmation"
51+
role="status"
52+
tabindex="-1"
53+
class="hidden px-7 py-2 text-green-400 font-mono font-bold text-lg rounded-sm"
54+
>
55+
<span aria-hidden="true"> &lt;!-- </span lang='en'>EMAIL COPIED!<span aria-hidden="true"> --&gt;</span>
56+
</div>
3357
</div>
3458
</div>
3559

3660
<div
61+
aria-hidden="true"
3762
class="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none"
3863
>
3964
</div>
@@ -46,10 +71,11 @@ import '@fontsource-variable/jetbrains-mono'
4671
let interval: number | null = null
4772

4873
const runMatrixEffect = (element: HTMLElement) => {
49-
let iteration = 0
74+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
75+
if (prefersReducedMotion) return
5076

77+
let iteration = 0
5178
clearInterval(interval as number)
52-
5379
const originalText = element.dataset.value || ''
5480

5581
interval = window.setInterval(() => {
@@ -72,44 +98,58 @@ import '@fontsource-variable/jetbrains-mono'
7298
}
7399

74100
const init = () => {
101+
const matrixText = document.getElementById('matrix-text')
75102
const h1 = document.querySelector('h1')
76103
const subtitle = document.querySelector('#subtitle')
77104
const actions = document.querySelector('#actions')
78105

79-
if (h1) {
80-
runMatrixEffect(h1)
81-
h1.onmouseover = () => runMatrixEffect(h1)
106+
if (matrixText && h1) {
107+
runMatrixEffect(matrixText)
108+
h1.onmouseover = () => runMatrixEffect(matrixText)
82109
}
83110

84111
if (subtitle) {
85112
setTimeout(() => {
86113
subtitle.textContent = 'Sede UB Barcelona | 6-8 Nov 2026'
114+
const container = document.getElementById('subtitle-container')
115+
if (container) {
116+
container.setAttribute('aria-busy', 'false')
117+
}
87118
if (actions) {
88119
actions.classList.remove('opacity-0')
89120
}
90121
}, 1500)
91122
}
92123

93-
//COPY MAIL
124+
// COPY MAIL LOGIC
94125
const sponsorBtn = document.getElementById('sponsor-btn')
95-
const sponsorText = document.getElementById('sponsor-text')
126+
const copyConfirmation = document.getElementById('copy-confirmation')
127+
const email = sponsorBtn?.dataset.email as string
96128

97-
if (sponsorBtn && sponsorText) {
129+
if (sponsorBtn && copyConfirmation && email) {
98130
sponsorBtn.onclick = async () => {
99131
try {
100-
await navigator.clipboard.writeText('sponsors@2026.es.pycon.org')
101-
const originalText = sponsorText.innerText
102-
sponsorText.innerText = '[ EMAIL COPIED! ]'
132+
await navigator.clipboard.writeText(email)
133+
134+
// Hide button, show confirmation
135+
sponsorBtn.classList.add('hidden')
136+
copyConfirmation.classList.remove('hidden')
137+
copyConfirmation.focus()
138+
103139
setTimeout(() => {
104-
sponsorText.innerText = originalText
140+
// Hide confirmation, show button and restore focus
141+
copyConfirmation.classList.add('hidden')
142+
sponsorBtn.classList.remove('hidden')
143+
sponsorBtn.focus()
105144
}, 2000)
106145
} catch (err) {
107146
console.error('Failed to copy', err)
108-
window.location.href = 'mailto:sponsors@2026.es.pycon.org'
147+
window.location.href = `mailto:${email}`
109148
}
110149
}
111150
}
112151
}
152+
113153
//not needed now
114154
document.addEventListener('astro:page-load', init)
115155
init()

0 commit comments

Comments
 (0)