Skip to content

Commit 3c2795f

Browse files
authored
Merge pull request #3843 from bgruening/fair_pimp
Pimp a bit the /fair page
2 parents c2f2dab + 15c73d1 commit 3c2795f

File tree

4 files changed

+291
-1
lines changed

4 files changed

+291
-1
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
interface FairSectionLink {
3+
key: string;
4+
slug: string;
5+
href: string;
6+
name: string;
7+
letter: string;
8+
summary: string;
9+
}
10+
11+
interface Props {
12+
sectionSlug: string;
13+
}
14+
15+
const { sectionSlug } = Astro.props;
16+
17+
const overviewHref = '/fair/';
18+
const sections: FairSectionLink[] = [
19+
{
20+
key: 'findability',
21+
slug: 'fair/findability',
22+
href: '/fair/findability/',
23+
name: 'Findability',
24+
letter: 'F',
25+
summary: 'Persistent IDs, metadata, and discovery pathways.',
26+
},
27+
{
28+
key: 'accessibility',
29+
slug: 'fair/accessibility',
30+
href: '/fair/accessibility/',
31+
name: 'Accessibility',
32+
letter: 'A',
33+
summary: 'Open access paths, storage connectors, and inclusive entry points.',
34+
},
35+
{
36+
key: 'interoperability',
37+
slug: 'fair/interoperability',
38+
href: '/fair/interoperability/',
39+
name: 'Interoperability',
40+
letter: 'I',
41+
summary: 'Standards, APIs, and exchange formats across research systems.',
42+
},
43+
{
44+
key: 'reusability',
45+
slug: 'fair/reusability',
46+
href: '/fair/reusability/',
47+
name: 'Reusability',
48+
letter: 'R',
49+
summary: 'Provenance, workflow sharing, and reproducible execution.',
50+
},
51+
];
52+
53+
const backLink = sectionSlug !== 'fair' ? { href: overviewHref, label: 'FAIR overview' } : undefined;
54+
---
55+
56+
<div class="fair-shell">
57+
<div class="fair-layout">
58+
<div id="fair-principles" class="fair-cards">
59+
{
60+
sections.map((section) => (
61+
<a
62+
href={section.href}
63+
class:list={['fair-card', section.slug === sectionSlug && 'is-active']}
64+
data-fair-card={section.key}
65+
aria-current={section.slug === sectionSlug ? 'page' : undefined}
66+
>
67+
<span class="fair-card-title">
68+
<span class="fair-letter">{section.letter}</span>
69+
<span class="fair-word-fragment">{section.name.slice(1)}</span>
70+
</span>
71+
<span class="fair-card-text">{section.summary}</span>
72+
</a>
73+
))
74+
}
75+
</div>
76+
<div class="fair-copy">
77+
<div class="fair-actions">
78+
<a
79+
href="https://training.galaxyproject.org/training-material/topics/fair/"
80+
target="_blank"
81+
rel="noopener noreferrer"
82+
class="fair-button fair-button-primary"
83+
>
84+
FAIR training in GTN
85+
</a>
86+
</div>
87+
{
88+
backLink && (
89+
<a href={backLink.href} class="fair-back-link">
90+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
91+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
92+
</svg>
93+
{backLink.label}
94+
</a>
95+
)
96+
}
97+
</div>
98+
</div>
99+
</div>
100+
101+
<style>
102+
.fair-shell {
103+
margin-top: 1.75rem;
104+
padding-top: 1.5rem;
105+
border-top: 1px solid rgba(255, 255, 255, 0.16);
106+
}
107+
108+
.fair-layout {
109+
display: grid;
110+
gap: 1.5rem;
111+
align-items: start;
112+
}
113+
114+
.fair-actions {
115+
display: flex;
116+
gap: 0.875rem;
117+
flex-wrap: wrap;
118+
margin-top: 1.25rem;
119+
}
120+
121+
.fair-button {
122+
display: inline-flex;
123+
align-items: center;
124+
justify-content: center;
125+
min-height: 2.75rem;
126+
padding: 0.75rem 1.25rem;
127+
border-radius: 0.5rem;
128+
font-size: 0.95rem;
129+
font-weight: 600;
130+
text-decoration: none;
131+
transition:
132+
transform 0.2s ease,
133+
background-color 0.2s ease,
134+
border-color 0.2s ease,
135+
color 0.2s ease;
136+
}
137+
138+
.fair-button:hover {
139+
transform: translateY(-1px);
140+
}
141+
142+
.fair-button-primary {
143+
background: var(--color-galaxy-gold);
144+
color: var(--color-galaxy-dark);
145+
border: 1px solid var(--color-galaxy-gold);
146+
}
147+
148+
.fair-button-primary:hover {
149+
background: #ffe660;
150+
}
151+
152+
.fair-back-link {
153+
display: inline-flex;
154+
align-items: center;
155+
gap: 0.375rem;
156+
margin-top: 1rem;
157+
color: rgba(255, 255, 255, 0.72);
158+
font-size: 0.875rem;
159+
text-decoration: none;
160+
transition: color 0.2s ease;
161+
}
162+
163+
.fair-back-link:hover {
164+
color: var(--color-galaxy-gold);
165+
}
166+
167+
.fair-cards {
168+
display: grid;
169+
grid-template-columns: repeat(4, minmax(0, 1fr));
170+
width: 100%;
171+
gap: 0.875rem;
172+
}
173+
174+
.fair-card {
175+
display: flex;
176+
flex-direction: column;
177+
gap: 0.65rem;
178+
padding: 0.85rem 0.95rem 0.95rem;
179+
border-radius: 1rem;
180+
border: 1px solid rgba(255, 255, 255, 0.14);
181+
background:
182+
linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03)), rgba(255, 255, 255, 0.02);
183+
color: white;
184+
text-decoration: none;
185+
transition:
186+
transform 0.2s ease,
187+
border-color 0.2s ease,
188+
box-shadow 0.2s ease;
189+
}
190+
191+
.fair-card:hover {
192+
transform: translateY(-2px);
193+
border-color: rgba(255, 215, 0, 0.55);
194+
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
195+
}
196+
197+
.fair-card.is-active {
198+
background: linear-gradient(180deg, rgba(255, 215, 0, 0.98), rgba(255, 215, 0, 0.82));
199+
border-color: rgba(255, 215, 0, 0.92);
200+
color: var(--color-galaxy-dark);
201+
}
202+
203+
.fair-card-title {
204+
display: flex;
205+
align-items: flex-start;
206+
gap: 0.12rem;
207+
font-weight: 700;
208+
line-height: 1;
209+
}
210+
211+
.fair-letter {
212+
flex-shrink: 0;
213+
font-size: clamp(2.5rem, 4vw, 3.4rem);
214+
font-weight: 800;
215+
line-height: 0.86;
216+
}
217+
218+
.fair-word-fragment {
219+
padding-top: 0.42rem;
220+
font-size: 0.98rem;
221+
letter-spacing: 0.02em;
222+
}
223+
224+
.fair-card-text {
225+
color: inherit;
226+
font-size: 0.95rem;
227+
line-height: 1.45;
228+
opacity: 0.92;
229+
}
230+
231+
@media (max-width: 960px) {
232+
.fair-cards {
233+
grid-template-columns: repeat(2, minmax(0, 1fr));
234+
}
235+
}
236+
237+
@media (max-width: 560px) {
238+
.fair-cards {
239+
grid-template-columns: 1fr;
240+
width: min(100%, 16rem);
241+
justify-self: center;
242+
}
243+
}
244+
</style>

astro/src/layouts/ArticleLayout.astro

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import BaseLayout from './BaseLayout.astro';
33
import PageHeader from '../components/layout/PageHeader.astro';
4+
import FairSectionHero from '../components/content/FairSectionHero.astro';
45
import SupporterGrid from '../components/content/SupporterGrid.astro';
56
import type { SupporterProfile } from '../utils/supporters';
67
import ContentWithToc from '../components/layout/ContentWithToc.astro';
@@ -27,6 +28,7 @@ interface Props {
2728
};
2829
supporters?: SupporterProfile[];
2930
sourceFile?: string;
31+
fairSection?: string;
3032
}
3133
3234
const {
@@ -41,6 +43,7 @@ const {
4143
headings = [],
4244
supporters: supportersRaw = [],
4345
sourceFile,
46+
fairSection,
4447
} = Astro.props;
4548
4649
const editUrl = sourceFile ? `https://github.com/galaxyproject/galaxy-hub/edit/main/content/${sourceFile}` : undefined;
@@ -57,7 +60,13 @@ const hasMetadata =
5760

5861
<BaseLayout title={title} description={description}>
5962
<article class="max-w-6xl mx-auto bg-white relative">
60-
{hasMetadata && <PageHeader title={title} description={description} date={date} authors={authors} tags={tags} />}
63+
{
64+
hasMetadata && (
65+
<PageHeader title={title} description={description} date={date} authors={authors} tags={tags}>
66+
{fairSection && <FairSectionHero sectionSlug={fairSection} />}
67+
</PageHeader>
68+
)
69+
}
6170

6271
{
6372
image && (

astro/src/pages/[...slug].astro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export async function getStaticPaths() {
3535
const { article } = Astro.props;
3636
const { Content, headings } = await render(article);
3737
const sourceFile = article.data.sourceFile || `${article.data.slug}/index.md`;
38+
const fairSection =
39+
article.data.slug === 'fair' || article.data.slug.startsWith('fair/') ? article.data.slug : undefined;
3840
3941
const { title, date, authors = [], tags = [], tease, image, autotoc } = article.data;
4042
@@ -65,6 +67,7 @@ const components = {
6567
headings={headings}
6668
autotoc={autotoc}
6769
sourceFile={sourceFile}
70+
fairSection={fairSection}
6871
>
6972
<Content components={components} />
7073
</ArticleLayout>

astro/tests/fair.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('FAIR pages', () => {
4+
test('/fair/ exposes all four principle cards', async ({ page }) => {
5+
const response = await page.goto('/fair/');
6+
expect(response?.status()).toBe(200);
7+
8+
await expect(page.locator('[data-fair-card="findability"]')).toHaveAttribute('href', '/fair/findability/');
9+
await expect(page.locator('[data-fair-card="accessibility"]')).toHaveAttribute('href', '/fair/accessibility/');
10+
await expect(page.locator('[data-fair-card="interoperability"]')).toHaveAttribute(
11+
'href',
12+
'/fair/interoperability/'
13+
);
14+
await expect(page.locator('[data-fair-card="reusability"]')).toHaveAttribute('href', '/fair/reusability/');
15+
});
16+
17+
test('FAIR subpages expose cross-navigation to the overview and sibling sections', async ({ page }) => {
18+
const response = await page.goto('/fair/findability/');
19+
expect(response?.status()).toBe(200);
20+
21+
await expect(page.getByRole('link', { name: 'FAIR overview' })).toHaveAttribute('href', '/fair/');
22+
await expect(page.locator('[data-fair-card="accessibility"]')).toHaveAttribute('href', '/fair/accessibility/');
23+
await expect(page.locator('[data-fair-card="interoperability"]')).toHaveAttribute(
24+
'href',
25+
'/fair/interoperability/'
26+
);
27+
await expect(page.locator('[data-fair-card="reusability"]')).toHaveAttribute('href', '/fair/reusability/');
28+
29+
await page.locator('[data-fair-card="reusability"]').click();
30+
await expect(page).toHaveURL('/fair/reusability/');
31+
await expect(page.getByRole('link', { name: 'FAIR overview' })).toHaveAttribute('href', '/fair/');
32+
await expect(page.locator('[data-fair-card="findability"]')).toHaveAttribute('href', '/fair/findability/');
33+
});
34+
});

0 commit comments

Comments
 (0)