Skip to content
Open
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
95 changes: 95 additions & 0 deletions src/components/FeatureCardGrid.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
interface Props {
items: {
title: string;
description?: string;
href?: string;
highlight?: boolean;
}[];
columns?: number;
}

const { items, columns = 4 } = Astro.props;
---

<div
class="feature-card-grid"
style={`--grid-columns: ${columns}`}
>
{items.map((item) =>
item.href ? (
<a
href={item.href}
class:list={["feature-card", item.highlight && "feature-card--highlight"]}
>
<strong class="feature-card-title">{item.title}</strong>
{item.description && <span class="feature-card-desc">{item.description}</span>}
</a>
) : (
<div class="feature-card">
<strong class="feature-card-title">{item.title}</strong>
{item.description && <span class="feature-card-desc">{item.description}</span>}
</div>
)
)}
</div>

<style is:global>
.feature-card-grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns, 4), 1fr);
gap: 6px;
list-style: none;
padding: 0;
margin: 0 0 2.5rem;
}

.feature-card {
display: flex;
flex-direction: column;
padding: 1.6rem;
font-size: 1.2rem;
color: var(--color-text-secondary);
line-height: 1.6;
border: 1px solid var(--color-border);
border-radius: 4px;
text-decoration: none;
transition: border-color 0.2s;
}

.feature-card:hover {
border-color: var(--color-accent-themed);
text-decoration: none;
}

.feature-card-title {
display: block;
font-size: 1.3rem;
font-weight: 800;
color: var(--color-text);
margin-bottom: 0.4rem;
}

.feature-card-desc {
font-size: 1.2rem;
color: var(--color-text-secondary);
}

.feature-card--highlight {
outline: 2px solid var(--color-accent-themed);
outline-offset: -1px;
background: var(--color-surface-medium);
}

@media (max-width: 900px) {
.feature-card-grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (max-width: 500px) {
.feature-card-grid {
grid-template-columns: 1fr;
}
}
</style>
4 changes: 2 additions & 2 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const existingSlugs = new Set(existingPages.map((p) => p.id));

// Standalone .astro pages that always exist regardless of content collection
const alwaysExist = new Set([
"sessions", "speakers", "schedule", "posters", "talks", "tutorials",
"sprints", "jobs", "sponsors", "community-partners", "media-partners",
"sessions", "speakers", "schedule", "schedule/talks", "schedule/tutorials", "posters", "talks", "tutorials",
"sprints", "jobs", "sponsors", "community-partners", "media-partners", "open-space",
]);

function linkExists(url: string): boolean {
Expand Down
152 changes: 152 additions & 0 deletions src/components/SessionCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
export interface Props {
title: string;
slug: string;
speakers?: string;
duration: string;
level?: string;
track?: string;
searchText?: string;
}

const { title, slug, speakers, duration, level, track, searchText } = Astro.props;
---
<div
class:list={["session-card", level && `level-${level}`]}
data-search={searchText ? searchText.toLowerCase() : `${title.toLowerCase()} ${(track ?? "").toLowerCase()}`}
>
<a href={`/session/${slug}`} class="card-title">{title}</a>

{speakers && <p class="card-speakers" set:html={speakers} />}

<div class="card-meta">
<span class="card-duration">{duration} min</span>
{level && (
<span class:list={["card-level", `card-level--${level}`]}>
{level.charAt(0).toUpperCase() + level.slice(1)}
</span>
)}
</div>

{track && <p class="card-track">{track}</p>}
</div>

<style>
.session-card {
display: flex;
flex-direction: column;
background: var(--color-surface-subtle);
border: 1px solid var(--color-border);
border-radius: 6px;
padding: 1.25rem;
transition: border-color 0.1s ease-in-out, background 0.1s ease-in-out;
}

.session-card:hover {
border-color: var(--ep-accent);
background: var(--ep-session-hover-bg) !important;
z-index: 10;
}

.session-card.level-beginner {
background:
repeating-linear-gradient(
-45deg,
transparent,
transparent 4px,
var(--ep-beginner-stripe) 4px,
var(--ep-beginner-stripe) 6px
),
var(--color-surface-subtle);
}

.session-card.level-advanced {
background:
repeating-linear-gradient(
-45deg,
transparent,
transparent 4px,
var(--ep-advanced-stripe) 4px,
var(--ep-advanced-stripe) 6px
),
var(--color-surface-subtle);
}

.card-title {
font-family: "Inter Tight", system-ui, sans-serif;
font-size: 1.25rem;
font-weight: 700;
color: var(--color-text);
margin: 0 0 0.5rem;
line-height: 1.35;
text-decoration: none;
}

.card-title:hover {
text-decoration: underline;
}

.card-speakers {
font-size: 1.15rem;
color: var(--color-text-secondary);
margin: 0 0 0.75rem;
}

:global(.card-speakers .speaker-link) {
color: var(--color-text-secondary);
text-decoration: none;
}

:global(.card-speakers .speaker-link:hover) {
text-decoration: underline;
}

.card-meta {
margin-top: auto;
padding-top: 2rem;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
align-items: center;
}

.card-duration {
font-size: 1.02rem;
color: var(--color-text-muted);
background: var(--color-surface-medium);
padding: 0.2em 0.6em;
border-radius: 3px;
border: 1px solid var(--color-border);
}

.card-level {
display: inline-block;
padding: 0.2em 0.6em;
border-radius: 3px;
font-size: 1.02rem;
font-weight: 600;
letter-spacing: 0.03em;
border: 1px solid transparent;
}

.card-level--beginner {
background: var(--ep-level-beginner-bg);
color: var(--ep-level-beginner-text);
}

.card-level--intermediate {
background: var(--ep-level-intermediate-bg);
color: var(--ep-level-intermediate-text);
}

.card-level--advanced {
background: var(--ep-level-advanced-bg);
color: var(--ep-level-advanced-text);
}

.card-track {
font-size: 0.95rem;
color: var(--color-text-muted);
margin-top: 0.5rem;
}
</style>
4 changes: 4 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const pages = defineCollection({
subtitle: z.string(),
toc: z.boolean().optional().default(true),
full: z.boolean().optional().default(false),
box1: z.string().optional(),
box2: z.string().optional(),
box3: z.string().optional(),
box4: z.string().optional(),
}),
});

Expand Down
16 changes: 9 additions & 7 deletions src/content/pages/_open-spaces.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
---
title: EuroPython 2025 Open Spaces
subtitle: Organise or join an open space at EuroPython 2025!
title: EuroPython 2026 Open Spaces
subtitle: Organise or join an open space at EuroPython 2026!
box1: "You set the agenda — Anyone can propose a session on any topic. Write it on the board in the morning and your room is booked."
box2: "Discussions, not presentations — Open spaces are conversations, not talks. Bring questions, ideas, or a demo and let the group take it from there."
box3: "The hallway track, formalised — Some of the best conference moments happen in hallway chats. Open spaces give those conversations a dedicated room and time slot."
box4: "All topics welcome — From deep technical discussions to community organising, hiring, or just playing board games — if people want to talk about it, it belongs here."
---

import { Image } from 'astro:assets';
# EuroPython 2026 Open Spaces

# EuroPython 2025 Open Spaces

At EuroPython 2025, Open Spaces offer a unique opportunity for attendees to
At EuroPython 2026, Open Spaces offer a unique opportunity for attendees to
shape their own experience. Instead of fixed schedules and talks, these sessions
are run by participants like you who bring fresh ideas and create discussions in
an open and friendly environment.
Expand Down Expand Up @@ -56,7 +58,7 @@ will reserve the slot for you.

## Who Can Participate?

Anyone attending EuroPython 2025 can join or lead an Open Space session. It’s a
Anyone attending EuroPython 2026 can join or lead an Open Space session. It’s a
perfect opportunity to share your ideas, ask questions, and connect with others
around topics you’re passionate about!

Expand Down
19 changes: 19 additions & 0 deletions src/content/pages/posters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Posters
subtitle:
A visual showcase of projects, research, and ideas — presented in person
during a dedicated poster session
box1:
"Visual format — Posters present a project, library, or research finding as a
visual display. Think of it as a paper you can walk up to and discuss."
box2:
"One-on-one conversations — Unlike talks, poster sessions let you have a
direct conversation with the author. Ask questions, give feedback, and dive as
deep as you like."
box3:
"Great for first-time speakers — Posters are an excellent way to share your
work without the pressure of a stage presentation. Many speakers start here."
box4:
"Dedicated session time — A block of time is reserved in the schedule for
poster viewing, so you won't have to choose between posters and talks."
---
18 changes: 18 additions & 0 deletions src/content/pages/talks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Talks
subtitle:
The heart of EuroPython — three days of talks across five parallel tracks
box1:
"30-minute talks — Our most common format. Speakers have 30 minutes including
Q&A to share a focused idea, project, or lesson learned."
box2:
"45-minute deep dives — Selected talks get an extended slot for more complex
topics that benefit from extra depth and live demonstrations."
box3:
"For every level — From beginner-friendly introductions to advanced internals
— each talk is tagged by experience level so you can plan your day."
box4:
"Community-selected — Talks are chosen through an open call for proposals and
community voting, ensuring the programme reflects what Pythonistas actually
want to learn."
---
18 changes: 18 additions & 0 deletions src/content/pages/tutorials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Tutorials
subtitle: Full-day, hands-on workshops led by domain experts
box1:
"3-hour or 6-hour sessions — Tutorials run for half a day or a full day. Each
one is a self-contained workshop with exercises, examples, and hands-on
practice."
box2:
"Small groups, personal attention — Unlike talks, tutorials are capped in
size. You get direct access to the instructor and can ask questions as you go."
box3:
"Wide range of topics — From Python basics and web frameworks to data science
pipelines, async programming, and testing strategies — there is a tutorial for
every interest."
box4:
"Separate ticket required — Tutorial participation requires a separate
tutorial ticket in addition to your conference pass."
---
2 changes: 1 addition & 1 deletion src/data/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface FooterColumn {

const L = {
// Programme
schedule: { label: "Schedule", url: "/schedule" },
schedule: { label: "Schedule", url: "/schedule/talks" },
talks: { label: "Talks", url: "/talks" },
tutorials: { label: "Tutorials", url: "/tutorials" },
posters: { label: "Posters", url: "/posters" },
Expand Down
Loading
Loading