Skip to content

Commit 328ea20

Browse files
francescarpiitziarZGctrl-alt-ddukebodyitziarAPSL
authored
Release Milestone 3 (#158)
En este milestone se incluye: - #138 - #139 - #140 - #141 - #142 - #146 - #150 - #153 - #154 --------- Co-authored-by: itziarZG <itziar.zameza.dev@gmail.com> Co-authored-by: Itziar ZG <70289074+itziarZG@users.noreply.github.com> Co-authored-by: Dani H <ctrl.alt.d@gmail.com> Co-authored-by: dukebody <dukebody@gmail.com> Co-authored-by: Itziar Zameza García <izameza@apsl.net>
1 parent 3520d08 commit 328ea20

20 files changed

Lines changed: 489 additions & 235 deletions

File tree

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ on:
55
branches: [main, develop]
66

77
jobs:
8-
build:
8+
check:
9+
name: Check
910
runs-on: ubuntu-latest
1011
steps:
1112
- name: Checkout
@@ -22,5 +23,11 @@ jobs:
2223
- name: Install dependencies
2324
run: pnpm install
2425

26+
- name: Astro check
27+
run: pnpm astro check
28+
29+
- name: Prettier check
30+
run: pnpm exec prettier --check src
31+
2532
- name: Build
2633
run: pnpm build

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434

3535
- name: Build with Astro
3636
run: pnpm run build
37+
env:
38+
PUBLIC_GA_ID: ${{ secrets.PUBLIC_GA_ID }}
3739

3840
- name: Upload artifact
3941
if: github.event_name != 'pull_request'

AGENTS.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,30 +162,59 @@ Agents (like Copilot) must adhere to these coding standards to ensure consistenc
162162
All new UI components and pages must be built with accessibility in mind from the start. Agents must prioritize the following core principles:
163163

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

169170
### 2. Text Alternatives (Images & Icons)
171+
170172
- **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=""`.
171173
- **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.
172174

173175
### 3. Color and Contrast
176+
174177
- **Contrast Ratios:** Ensure all text has sufficient contrast against its background. Avoid light grey text on white backgrounds.
175-
- **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").
178+
- **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").
176179

177180
### 4. Keyboard Navigation
181+
178182
- **Tab navigation:** All interactive elements must be fully functional using only the keyboard (`Tab` and `Enter`/`Space`).
179183
- **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`).
180184
185+
### 5. External Links & New Tab Notifications
186+
187+
- **New tab links:** All links with `target="_blank"` must include `aria-label` that informs users the link opens in a new tab.
188+
- **Use centralized translations:** Use `menuTexts[lang].new_tab` for consistent translations across all languages:
189+
- Spanish: `(se abre en nueva pestaña)`
190+
- English: `(opens in new tab)`
191+
- Catalan: `(s'obre en una pestanya nova)`
192+
- **Implementation example:**
193+
```astro
194+
<a
195+
href="https://example.com"
196+
target="_blank"
197+
rel="noopener noreferrer"
198+
aria-label={`${linkText} ${menuTexts[lang].new_tab}`}
199+
>
200+
{linkText}
201+
</a>
202+
```
203+
- **Required imports:** Always import `menuTexts` when adding external links:
204+
```astro
205+
import {menuTexts} from '@/i18n/menu' const menuT = menuTexts[lang as keyof typeof menuTexts]
206+
```
207+
181208
### Agent Enforcement
209+
182210
- When generating or modifying components, agents **must** proactively apply these accessibility standards without needing explicit prompting from the user.
183211

184212
## 5. SEO and Page Creation Guidelines
185213

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

188216
### Multi-language Pages
217+
189218
- All new pages must be placed in `src/pages/[lang]/`.
190219
- Use `getStaticPaths()` to support all configured locales (`es`, `en`, `ca`).
191220
- Example structure:
@@ -196,17 +225,20 @@ Agents must ensure all new pages are optimized for search engines and follow the
196225
```
197226

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

203233
### Semantic HTML and Accessibility
234+
204235
- **H1 Tags**: Use exactly one `<h1>` per page.
205236
- **Headings**: Maintain a logical hierarchy (`h2`, `h3`, etc.).
206237
- **Images**: All `<img>` tags must include a descriptive `alt` attribute.
207238
- **Links**: Use descriptive text for links. Avoid generic phrases like "click here".
208239

209240
### Analytics and Monitoring
241+
210242
- Use the `PUBLIC_GA_ID` environment variable for Google Analytics.
211243
- Do not hardcode tracking IDs.
212244

Lines changed: 114 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { accommodationTexts } from '../i18n/accommodation'
3+
import { menuTexts } from '../i18n/menu'
34
import StatusIcon from './icons/StatusIcon.astro'
45
56
interface Props {
@@ -8,6 +9,25 @@ interface Props {
89
910
const { lang } = Astro.props
1011
const t = accommodationTexts[lang as keyof typeof accommodationTexts]
12+
const menuT = menuTexts[lang as keyof typeof menuTexts]
13+
type AreaSlug =
14+
| 'eixampleEsquerra'
15+
| 'elRaval'
16+
| 'santAntoni'
17+
| 'ciutatVella'
18+
| 'sants'
19+
| 'poblenou22'
20+
| 'elClot'
21+
| 'lesCorts'
22+
| 'eixampleDreta'
23+
| 'collblanc'
24+
25+
const walkingAreas: AreaSlug[] = ['eixampleEsquerra', 'elRaval', 'santAntoni', 'ciutatVella']
26+
const directMetroAreas: AreaSlug[] = ['sants', 'poblenou22', 'elClot']
27+
const transferAreas: AreaSlug[] = ['lesCorts', 'eixampleDreta', 'collblanc']
28+
29+
const areaName = (slug: AreaSlug) => t[`accommodation.areas.${slug}.name` as keyof typeof t]
30+
const areaDesc = (slug: AreaSlug) => t[`accommodation.areas.${slug}.desc` as keyof typeof t]
1131
---
1232

1333
<div class="accommodation-container pb-20">
@@ -20,33 +40,91 @@ const t = accommodationTexts[lang as keyof typeof accommodationTexts]
2040
</p>
2141
</section>
2242

23-
<section
24-
class="max-w-4xl bg-white/5 backdrop-blur-md p-8 md:p-12 rounded-3xl border border-white/10 mb-16 shadow-2xl"
25-
>
26-
<p class="text-lg text-pycon-gray-25 leading-relaxed">
27-
{t['accommodation.intro']}
28-
</p>
43+
<!-- Areas -->
44+
<section class="mb-24" aria-labelledby="accommodation-areas-heading">
45+
<div class="flex items-center gap-4 mb-12">
46+
<div class="h-px bg-white/20 flex-1" aria-hidden="true"></div>
47+
<h2 id="accommodation-areas-heading" class="text-3xl font-bold text-white px-4">
48+
{t['accommodation.areas.title']}
49+
</h2>
50+
<div class="h-px bg-white/20 flex-1" aria-hidden="true"></div>
51+
</div>
52+
<div class="max-w-4xl space-y-4 mb-12 text-pycon-gray-25 leading-relaxed">
53+
<p class="text-lg">{t['accommodation.areas.transportIntro.p1']}</p>
54+
<p class="text-lg">{t['accommodation.areas.transportIntro.p2']}</p>
55+
<p class="text-lg">{t['accommodation.areas.transportIntro.p3']}</p>
56+
</div>
57+
58+
<div class="space-y-16">
59+
<div>
60+
<h3 class="text-2xl font-bold text-white mb-6">{t['accommodation.areas.category.walking']}</h3>
61+
<ul class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8 list-none m-0 p-0">
62+
{
63+
walkingAreas.map((slug) => (
64+
<li class="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">
65+
<h4 class="text-xl font-bold text-pycon-orange mb-4 italic">{areaName(slug)}</h4>
66+
<p class="text-pycon-gray-25 text-sm leading-relaxed">{areaDesc(slug)}</p>
67+
</li>
68+
))
69+
}
70+
</ul>
71+
</div>
72+
<div>
73+
<h3 class="text-2xl font-bold text-white mb-6">{t['accommodation.areas.category.directMetro']}</h3>
74+
<ul class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8 list-none m-0 p-0">
75+
{
76+
directMetroAreas.map((slug) => (
77+
<li class="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">
78+
<h4 class="text-xl font-bold text-pycon-orange mb-4 italic">{areaName(slug)}</h4>
79+
<p class="text-pycon-gray-25 text-sm leading-relaxed">{areaDesc(slug)}</p>
80+
</li>
81+
))
82+
}
83+
</ul>
84+
</div>
85+
<div>
86+
<h3 class="text-2xl font-bold text-white mb-6">{t['accommodation.areas.category.transferMetro']}</h3>
87+
<ul class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8 list-none m-0 p-0">
88+
{
89+
transferAreas.map((slug) => (
90+
<li class="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">
91+
<h4 class="text-xl font-bold text-pycon-orange mb-4 italic">{areaName(slug)}</h4>
92+
<p class="text-pycon-gray-25 text-sm leading-relaxed">{areaDesc(slug)}</p>
93+
</li>
94+
))
95+
}
96+
</ul>
97+
</div>
98+
</div>
2999
</section>
30100

31101
<!-- Hotels Section -->
32-
<section class="mb-24">
102+
<section class="mb-24" aria-labelledby="accommodation-hotels-heading">
33103
<div class="flex items-center gap-4 mb-12">
34-
<div class="h-px bg-white/20 flex-1"></div>
35-
<h2 class="text-3xl font-bold text-white px-4">{t['accommodation.hotels.title']}</h2>
36-
<div class="h-px bg-white/20 flex-1"></div>
104+
<div class="h-px bg-white/20 flex-1" aria-hidden="true"></div>
105+
<h2 id="accommodation-hotels-heading" class="text-3xl font-bold text-white px-4">
106+
{t['accommodation.hotels.title']}
107+
</h2>
108+
<div class="h-px bg-white/20 flex-1" aria-hidden="true"></div>
37109
</div>
38110

39111
<div
40112
class="bg-pycon-gray-100/50 p-12 rounded-3xl border border-pycon-yellow/30 text-center relative overflow-hidden group"
41113
>
42114
<!-- Shine effect -->
43115
<div
44-
class="absolute -top-full -left-full w-[300%] h-[300%] bg-linear-to-br from-transparent via-pycon-yellow/5 to-transparent rotate-45 transition-transform duration-1000 group-hover:translate-x-[33%] group-hover:translate-y-[33%] pointer-events-none"
116+
class="absolute -top-full -left-full w-[300%] h-[300%] bg-linear-to-br from-transparent via-pycon-yellow/5 to-transparent rotate-45 transition-transform duration-1000 motion-safe:group-hover:translate-x-[33%] motion-safe:group-hover:translate-y-[33%] pointer-events-none"
117+
aria-hidden="true"
45118
>
46119
</div>
47120

48121
<div class="relative z-10">
49-
<StatusIcon type="construction" size="xl" role="img" aria-label="Construyendo" />
122+
<StatusIcon
123+
type="construction"
124+
size="xl"
125+
role="img"
126+
aria-label={t['accommodation.a11y.constructionIcon']}
127+
/>
50128
<h3 class="text-2xl font-bold text-pycon-yellow mb-4">{t['accommodation.hotels.subtitle']}</h3>
51129
<p class="text-xl text-white font-medium max-w-2xl mx-auto italic opacity-80">
52130
{t['accommodation.hotels.disclaimer']}
@@ -55,59 +133,37 @@ const t = accommodationTexts[lang as keyof typeof accommodationTexts]
55133
</div>
56134
</section>
57135

58-
<!-- Areas -->
59-
<section class="mb-24">
60-
<h2 class="text-3xl font-bold text-white mb-12 flex items-center gap-3">
61-
<span class="w-2 h-8 bg-pycon-red rounded-full"></span>
62-
{t['accommodation.areas.title']}
63-
</h2>
64-
<div class="grid md:grid-cols-3 gap-8">
65-
<div
66-
class="bg-pycon-black/40 p-8 rounded-2xl border border-white/5 hover:border-pycon-orange/50 transition-all hover:-translate-y-2"
67-
>
68-
<h3 class="text-xl font-bold text-pycon-orange mb-4 italic">
69-
{t['accommodation.areas.eixample.name']}
70-
</h3>
71-
<p class="text-pycon-gray-25 text-sm leading-relaxed">{t['accommodation.areas.eixample.desc']}</p>
72-
</div>
73-
<div
74-
class="bg-pycon-black/40 p-8 rounded-2xl border border-white/5 hover:border-pycon-orange/50 transition-all hover:-translate-y-2 text-center"
75-
>
76-
<h3 class="text-xl font-bold text-pycon-orange mb-4 italic">
77-
{t['accommodation.areas.gothic.name']}
78-
</h3>
79-
<p class="text-pycon-gray-25 text-sm leading-relaxed">{t['accommodation.areas.gothic.desc']}</p>
80-
</div>
81-
<div
82-
class="bg-pycon-black/40 p-8 rounded-2xl border border-white/5 hover:border-pycon-orange/50 transition-all hover:-translate-y-2 text-right"
83-
>
84-
<h3 class="text-xl font-bold text-pycon-orange mb-4 italic">
85-
{t['accommodation.areas.poblenou.name']}
86-
</h3>
87-
<p class="text-pycon-gray-25 text-sm leading-relaxed">{t['accommodation.areas.poblenou.desc']}</p>
88-
</div>
89-
</div>
90-
</section>
91-
92136
<!-- Apartments -->
93137
<section
94138
class="bg-linear-to-r from-pycon-red/20 to-pycon-orange/20 p-8 md:p-16 rounded-3xl border border-pycon-red/30"
139+
aria-labelledby="accommodation-apartments-heading"
95140
>
96141
<div class="flex flex-col md:flex-row gap-12 items-center">
97142
<div class="flex-1">
98-
<h2 class="text-3xl font-bold text-white mb-6 uppercase tracking-tight">
143+
<h2
144+
id="accommodation-apartments-heading"
145+
class="text-3xl font-bold text-white mb-6 uppercase tracking-tight"
146+
>
99147
{t['accommodation.apartments.title']}
100148
</h2>
101149
<p class="text-pycon-gray-25 text-lg mb-8 leading-relaxed">
102150
{t['accommodation.apartments.text']}
103151
</p>
104152
<a
105-
href="https://meet.barcelona/es/alojarse-en-barcelona"
153+
href={t['accommodation.apartments.linkUrl']}
106154
target="_blank"
107-
class="inline-flex items-center gap-2 bg-white text-pycon-red font-bold px-6 py-3 rounded-lg hover:scale-105 transition-transform no-underline"
155+
rel="noopener noreferrer"
156+
aria-label={`${t['accommodation.apartments.link']} ${menuT.new_tab}`}
157+
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"
108158
>
109159
{t['accommodation.apartments.link']}
110-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
160+
<span class="sr-only"> ({t['accommodation.a11y.linkOpensNewTab']})</span>
161+
<svg
162+
class="w-5 h-5 shrink-0"
163+
fill="none"
164+
stroke="currentColor"
165+
viewBox="0 0 24 24"
166+
aria-hidden="true"
111167
><path
112168
stroke-linecap="round"
113169
stroke-linejoin="round"
@@ -118,8 +174,9 @@ const t = accommodationTexts[lang as keyof typeof accommodationTexts]
118174
</div>
119175
<div
120176
class="w-48 h-48 bg-white/10 rounded-full flex items-center justify-center border border-white/20 shadow-2xl backdrop-blur-xl shrink-0"
177+
aria-hidden="true"
121178
>
122-
<StatusIcon type="city" size="xl" role="img" aria-label="Ubicación" />
179+
<StatusIcon type="city" size="xl" />
123180
</div>
124181
</div>
125182
</section>
@@ -140,4 +197,10 @@ const t = accommodationTexts[lang as keyof typeof accommodationTexts]
140197
transform: translateY(0);
141198
}
142199
}
200+
201+
@media (prefers-reduced-motion: reduce) {
202+
.accommodation-container {
203+
animation: none;
204+
}
205+
}
143206
</style>

src/components/Button.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const sizes = {
2424
}
2525
2626
const baseClasses =
27-
"inline-flex items-center justify-center font-['Outfit'] font-medium rounded-lg text-center transition-colors duration-200 cursor-pointer"
27+
'inline-flex items-center justify-center font-medium rounded-lg text-center transition-colors duration-200 cursor-pointer'
2828
2929
const Element = href ? 'a' : 'button'
3030
---

src/components/LocationPage.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
---
22
import { locationTexts } from '../i18n/location'
3+
import { menuTexts } from '../i18n/menu'
34
45
interface Props {
56
lang: string
67
}
78
89
const { lang } = Astro.props
910
const t = locationTexts[lang as keyof typeof locationTexts]
11+
const menuT = menuTexts[lang as keyof typeof menuTexts]
1012
1113
const transportIcons = {
1214
metro: 'M12 2L4.5 20.29L5.21 21L12 18L18.79 21L19.5 20.29L12 2Z',
@@ -19,7 +21,7 @@ const transportIcons = {
1921

2022
<div class="location-container pb-20">
2123
<!-- Hero Section -->
22-
<section class="relative h-[60vh] min-h-[400px] w-full overflow-hidden rounded-3xl mb-12 shadow-2xl">
24+
<section class="relative h-[40vh] min-h-10 w-full overflow-hidden rounded-3xl mb-12 shadow-2xl">
2325
<img
2426
src="/images/Barcelona/seuUB.jpg"
2527
alt={t['location.hero.title']}
@@ -129,6 +131,7 @@ const transportIcons = {
129131
href="https://maps.app.goo.gl/bMt7iLyNT2N2Et786"
130132
target="_blank"
131133
rel="noopener noreferrer"
134+
aria-label={`${t['location.map.link']} ${menuT.new_tab}`}
132135
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"
133136
>
134137
{t['location.map.link']}

0 commit comments

Comments
 (0)