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
34 changes: 33 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,30 +162,59 @@ Agents (like Copilot) must adhere to these coding standards to ensure consistenc
All new UI components and pages must be built with accessibility in mind from the start. Agents must prioritize the following core principles:

### 1. Semantic HTML & Structure

- **Use HTML5 elements:** Prioritize `<header>`, `<nav>`, `<main>`, `<section>`, `<article>`, and `<footer>` over generic `<div>`s. Screen readers depend on this semantics.
- **Heading hierarchy:** Always use headings in logical order (`<h1>` → `<h2>` → `<h3>`) without skipping levels.
- **Actions vs Navigation:** Use `<button>` for actions and `<a>` solely for links/navigation. Avoid using `<div>` or `<span>` with `onClick` handlers.

### 2. Text Alternatives (Images & Icons)

- **Image `alt` tags:** All `<img>` elements must have meaningful `alt` text. Describe the image's function or content (e.g., `alt="Persona usando la app en un celular"` not `alt="imagen1"`). If an image is purely decorative, strictly use `alt=""`.
- **SVGs and ARIA:** Ensure decorative SVGs have `aria-hidden="true"`. Interactive SVGs must have an `aria-label` or `<title>`. Provide `aria-` attributes (`aria-expanded`, `aria-describedby`) for dynamic elements where visual context isn't enough.

### 3. Color and Contrast

- **Contrast Ratios:** Ensure all text has sufficient contrast against its background. Avoid light grey text on white backgrounds.
- **Do not rely on color alone:** Always provide an additional visual indicator alongside color (e.g., rather than saying "Fields in red are required", say "Fields marked with * are required").
- **Do not rely on color alone:** Always provide an additional visual indicator alongside color (e.g., rather than saying "Fields in red are required", say "Fields marked with \* are required").

### 4. Keyboard Navigation

- **Tab navigation:** All interactive elements must be fully functional using only the keyboard (`Tab` and `Enter`/`Space`).
- **Visible Focus:** Ensure a clear, visible focus state for all focusable elements. Never use `outline: none;` without providing a custom visible focus ring (e.g., using Tailwind's `focus-visible:ring`).

### 5. External Links & New Tab Notifications

- **New tab links:** All links with `target="_blank"` must include `aria-label` that informs users the link opens in a new tab.
- **Use centralized translations:** Use `menuTexts[lang].new_tab` for consistent translations across all languages:
- Spanish: `(se abre en nueva pestaña)`
- English: `(opens in new tab)`
- Catalan: `(s'obre en una pestanya nova)`
- **Implementation example:**
```astro
<a
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
aria-label={`${linkText} ${menuTexts[lang].new_tab}`}
>
{linkText}
</a>
```
- **Required imports:** Always import `menuTexts` when adding external links:
```astro
import {menuTexts} from '@/i18n/menu' const menuT = menuTexts[lang as keyof typeof menuTexts]
```

### Agent Enforcement

- When generating or modifying components, agents **must** proactively apply these accessibility standards without needing explicit prompting from the user.

## 5. SEO and Page Creation Guidelines

Agents must ensure all new pages are optimized for search engines and follow the project's internationalization (i18n) structure.

### Multi-language Pages

- All new pages must be placed in `src/pages/[lang]/`.
- Use `getStaticPaths()` to support all configured locales (`es`, `en`, `ca`).
- Example structure:
Expand All @@ -196,17 +225,20 @@ Agents must ensure all new pages are optimized for search engines and follow the
```

### Layout and Metadata

- Every page **must** use the `Layout` component from `src/layouts/Layout.astro`.
- Pass a unique and descriptive `title` and `description` (150-160 characters) to the `Layout` component.
- The `Layout` component automatically handles canonical URLs, social media tags (OG/Twitter), and `hreflang` tags.

### Semantic HTML and Accessibility

- **H1 Tags**: Use exactly one `<h1>` per page.
- **Headings**: Maintain a logical hierarchy (`h2`, `h3`, etc.).
- **Images**: All `<img>` tags must include a descriptive `alt` attribute.
- **Links**: Use descriptive text for links. Avoid generic phrases like "click here".

### Analytics and Monitoring

- Use the `PUBLIC_GA_ID` environment variable for Google Analytics.
- Do not hardcode tracking IDs.

Expand Down
3 changes: 3 additions & 0 deletions src/components/AccommodationPage.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { accommodationTexts } from '../i18n/accommodation'
import { menuTexts } from '../i18n/menu'
import StatusIcon from './icons/StatusIcon.astro'

interface Props {
Expand All @@ -8,6 +9,7 @@ interface Props {

const { lang } = Astro.props
const t = accommodationTexts[lang as keyof typeof accommodationTexts]
const menuT = menuTexts[lang as keyof typeof menuTexts]
type AreaSlug =
| 'eixampleEsquerra'
| 'elRaval'
Expand Down Expand Up @@ -151,6 +153,7 @@ const areaDesc = (slug: AreaSlug) => t[`accommodation.areas.${slug}.desc` as key
href={t['accommodation.apartments.linkUrl']}
target="_blank"
rel="noopener noreferrer"
aria-label={`${t['accommodation.apartments.link']} ${menuT.new_tab}`}
class="inline-flex items-center gap-2 bg-white text-pycon-red font-bold px-6 py-3 rounded-lg motion-safe:hover:scale-105 transition-transform no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pycon-yellow"
>
{t['accommodation.apartments.link']}
Expand Down
3 changes: 3 additions & 0 deletions src/components/LocationPage.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
import { locationTexts } from '../i18n/location'
import { menuTexts } from '../i18n/menu'

interface Props {
lang: string
}

const { lang } = Astro.props
const t = locationTexts[lang as keyof typeof locationTexts]
const menuT = menuTexts[lang as keyof typeof menuTexts]

const transportIcons = {
metro: 'M12 2L4.5 20.29L5.21 21L12 18L18.79 21L19.5 20.29L12 2Z',
Expand Down Expand Up @@ -129,6 +131,7 @@ const transportIcons = {
href="https://maps.app.goo.gl/bMt7iLyNT2N2Et786"
target="_blank"
rel="noopener noreferrer"
aria-label={`${t['location.map.link']} ${menuT.new_tab}`}
class="inline-block w-full text-center bg-white text-pycon-red font-bold py-3 rounded-lg hover:bg-pycon-gray-25 transition-colors shadow-lg"
>
{t['location.map.link']}
Expand Down
1 change: 1 addition & 0 deletions src/i18n/menu/ca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export const ca = {
],
},
],
new_tab: "(s'obre en una pestanya nova)",
} as const
1 change: 1 addition & 0 deletions src/i18n/menu/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export const en = {
],
},
],
new_tab: '(opens in new tab)',
} as const
1 change: 1 addition & 0 deletions src/i18n/menu/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export const es = {
],
},
],
new_tab: '(se abre en nueva pestaña)',
} as const
5 changes: 4 additions & 1 deletion src/layouts/components/Footer.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { getRelativeLocaleUrl } from 'astro:i18n'
import { footerTexts } from '@/i18n/components/footer'
import { menuTexts } from '@/i18n/menu'
import SocialIcon from '@/components/icons/SocialIcon.astro'

interface Props {
Expand All @@ -17,6 +18,7 @@ type Lang = (typeof validLangs)[number]
const currentLang: Lang = validLangs.includes(rawLang as Lang) ? (rawLang as Lang) : 'es'

const t = footerTexts[currentLang]
const menuT = menuTexts[currentLang]

// Updated social links with semantic icon names
const socialLinks = [
Expand Down Expand Up @@ -102,7 +104,7 @@ const collaborators = [
target="_blank"
rel="noopener noreferrer"
class="group flex h-9 w-9 items-center justify-center rounded-lg bg-pycon-gray-100/10 bg-pycon-gray-100/20 border border-pycon-gray-100/10 text-pycon-black text-pycon-white transition-all hover:bg-pycon-yellow hover:text-pycon-black hover:scale-110 outline-none focus-visible:bg-pycon-yellow focus-visible:text-pycon-black"
aria-label={link.label}
aria-label={`${link.label} ${menuT.new_tab}`}
title={link.label}
>
<SocialIcon platform={link.iconName} size="md" aria-hidden="true" />
Expand All @@ -128,6 +130,7 @@ const collaborators = [
target="_blank"
rel="noopener noreferrer"
class="group transition-all hover:scale-105 outline-none focus-visible:ring-1 focus-visible:ring-pycon-orange rounded-md p-1"
aria-label={`${collab.name} ${menuT.new_tab}`}
title={collab.name}
>
<img
Expand Down
12 changes: 10 additions & 2 deletions src/layouts/components/Header/components/Navigation.astro
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ const { items } = menuTexts[currentLang]
class="submenu-item block px-4 py-3 rounded-md text-pycon-white font-medium transition-colors outline-none hover:text-pycon-yellow hover:bg-pycon-yellow-25/15 focus-visible:bg-pycon-orange focus-visible:text-white"
role="menuitem"
{...(isExternal(child.href)
? { target: '_blank', rel: 'noopener noreferrer' }
? {
target: '_blank',
rel: 'noopener noreferrer',
'aria-label': `${child.label} ${menuTexts[currentLang].new_tab}`,
}
: {})}
>
{child.label}
Expand Down Expand Up @@ -172,7 +176,11 @@ const { items } = menuTexts[currentLang]
href={getLocalizedUrl(currentLang, child.href)}
class="block px-4 py-3 rounded-md text-pycon-white font-medium transition-colors outline-none hover:text-pycon-yellow hover:bg-pycon-yellow-25/15 focus-visible:bg-pycon-orange focus-visible:text-white"
{...(isExternal(child.href)
? { target: '_blank', rel: 'noopener noreferrer' }
? {
target: '_blank',
rel: 'noopener noreferrer',
'aria-label': `${child.label} ${menuTexts[currentLang].new_tab}`,
}
: {})}
>
{child.label}
Expand Down
Loading