diff --git a/src/components/FeatureCardGrid.astro b/src/components/FeatureCardGrid.astro new file mode 100644 index 000000000..9fe223459 --- /dev/null +++ b/src/components/FeatureCardGrid.astro @@ -0,0 +1,95 @@ +--- +interface Props { + items: { + title: string; + description?: string; + href?: string; + highlight?: boolean; + }[]; + columns?: number; +} + +const { items, columns = 4 } = Astro.props; +--- + +
+ {items.map((item) => + item.href ? ( + + {item.title} + {item.description && {item.description}} + + ) : ( +
+ {item.title} + {item.description && {item.description}} +
+ ) + )} +
+ + diff --git a/src/components/Header.astro b/src/components/Header.astro index 8cc38b96c..95ee55ff1 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -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 { diff --git a/src/components/SessionCard.astro b/src/components/SessionCard.astro new file mode 100644 index 000000000..d0b17f39e --- /dev/null +++ b/src/components/SessionCard.astro @@ -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; +--- +
+ {title} + + {speakers &&

} + +

+ {duration} min + {level && ( + + {level.charAt(0).toUpperCase() + level.slice(1)} + + )} +
+ + {track &&

{track}

} +
+ + diff --git a/src/content.config.ts b/src/content.config.ts index 93fd268dd..a39c41d39 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -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(), }), }); diff --git a/src/content/pages/_open-spaces.mdx b/src/content/pages/_open-spaces.mdx index c0bdcf27c..1a77990e1 100644 --- a/src/content/pages/_open-spaces.mdx +++ b/src/content/pages/_open-spaces.mdx @@ -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. @@ -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! diff --git a/src/content/pages/posters.md b/src/content/pages/posters.md new file mode 100644 index 000000000..4ec068ba4 --- /dev/null +++ b/src/content/pages/posters.md @@ -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." +--- diff --git a/src/content/pages/talks.md b/src/content/pages/talks.md new file mode 100644 index 000000000..ef8c637e8 --- /dev/null +++ b/src/content/pages/talks.md @@ -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." +--- diff --git a/src/content/pages/tutorials.md b/src/content/pages/tutorials.md new file mode 100644 index 000000000..cd3d9e337 --- /dev/null +++ b/src/content/pages/tutorials.md @@ -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." +--- diff --git a/src/data/nav.ts b/src/data/nav.ts index 632252f16..1d096d885 100644 --- a/src/data/nav.ts +++ b/src/data/nav.ts @@ -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" }, diff --git a/src/layouts/ScheduleLayout.astro b/src/layouts/ScheduleLayout.astro index a9c517cdd..85a3b24a3 100644 --- a/src/layouts/ScheduleLayout.astro +++ b/src/layouts/ScheduleLayout.astro @@ -7,9 +7,10 @@ export interface Props { title?: string; description: string; headline: string; + hideNotices?: string[]; } -const { title, description, headline } = Astro.props; +const { title, description, headline, hideNotices = [] } = Astro.props; --- @@ -27,6 +28,45 @@ const { title, description, headline } = Astro.props;

{headline}

+ +
@@ -56,6 +96,64 @@ main { } +/* Schedule notice grid */ +.sched-notice-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1.2rem; + margin-bottom: 2.5rem; +} + +.sched-notice-card { + display: flex; + flex-direction: column; + padding: 1.4rem; + border: 1px solid var(--ep-border); + border-radius: 6px; + text-decoration: none; + transition: border-color 0.2s, background 0.2s; + background: var(--ep-surface-subtle); +} + +.sched-notice-card:hover { + border-color: var(--ep-accent); + background: var(--ep-session-hover-bg); +} + +.sched-notice-card strong { + font-family: 'Inter Tight', system-ui, sans-serif; + font-weight: 800; + font-size: 1.2rem; + color: var(--ep-white); + margin-bottom: 0.3rem; +} + +.sched-notice-card span { + font-size: 0.95rem; + color: var(--ep-text-secondary); + line-height: 1.5; +} + +.sched-notice-date { + font-family: 'Inter Tight', system-ui, sans-serif; + font-weight: 700; + font-size: 1rem !important; + color: var(--color-accent-themed) !important; + margin-top: 0.5rem; +} + +@media (max-width: 900px) { + .sched-notice-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 500px) { + .sched-notice-grid { + grid-template-columns: 1fr; + } +} + /* Unset Tailwind light theme on schedule pages */ .text-gray-600, .text-gray-700, .text-gray-800, .text-gray-900, .text-gray-500, .text-gray-400 { diff --git a/src/pages/open-spaces.astro b/src/pages/open-spaces.astro new file mode 100644 index 000000000..c23f33821 --- /dev/null +++ b/src/pages/open-spaces.astro @@ -0,0 +1,140 @@ +--- +import Layout from "@layouts/Layout.astro"; +import Section2 from "@ui/Section2.astro"; +import Title from "@ui/Title.astro"; +import Markdown from "@ui/Markdown.astro"; +import { getEntry } from "astro:content"; + +const articleEntry = await getEntry("pages", "open-spaces"); +--- + + + + + + + + +
+ +
+ + Open Spaces + +
+ Wednesday – Friday, July 15–17 + Dedicated open space rooms +
+
+ +
    + {articleEntry && ["box1", "box2", "box3", "box4"].map((box) => { + const text = articleEntry.data[box as keyof typeof articleEntry.data]; + if (!text) return null; + const [title, ...rest] = text.split(" — "); + return ( +
  • + {title} + {rest.join(" — ")} +
  • + ); + })} +
+ + {articleEntry && ( +
+ +
+ )} + +
+
+ +
+ + diff --git a/src/pages/posters.astro b/src/pages/posters.astro index c6f4ca5d3..41ae38b30 100644 --- a/src/pages/posters.astro +++ b/src/pages/posters.astro @@ -2,9 +2,11 @@ import Layout from "@layouts/Layout.astro"; import Section2 from "@ui/Section2.astro"; import Title from "@ui/Title.astro"; -import { getCollection } from "astro:content"; +import { getCollection, getEntry } from "astro:content"; import ListPosters from "@components/sessions/list-posters.astro"; +const featuresEntry = await getEntry("pages", "posters"); + const allSessions = await getCollection("sessions"); const posters = allSessions .filter((s) => s.data.session_type?.toLowerCase() === "poster") @@ -28,28 +30,19 @@ const posters = allSessions
- + diff --git a/src/pages/schedule/talks.astro b/src/pages/schedule/talks.astro index 48d29be07..11c5d1f72 100644 --- a/src/pages/schedule/talks.astro +++ b/src/pages/schedule/talks.astro @@ -24,7 +24,7 @@ const talkDays = new Set(["2026-07-15", "2026-07-16", "2026-07-17"]); --- - + { days .filter((day) => talkDays.has(day.id)) diff --git a/src/pages/schedule/tutorials.astro b/src/pages/schedule/tutorials.astro index 3fbaaef40..5f630fe31 100644 --- a/src/pages/schedule/tutorials.astro +++ b/src/pages/schedule/tutorials.astro @@ -24,7 +24,7 @@ const tutorialDays = new Set(["2026-07-13", "2026-07-14"]); --- - + { days .filter((day) => tutorialDays.has(day.id)) diff --git a/src/pages/session/[slug].astro b/src/pages/session/[slug].astro index 4f973c762..8ff64beb4 100644 --- a/src/pages/session/[slug].astro +++ b/src/pages/session/[slug].astro @@ -8,6 +8,7 @@ import { YouTube } from "@astro-community/astro-embed-youtube"; import { Picture } from "astro:assets"; import Markdown from "@ui/Markdown.astro"; import Section2 from "@ui/Section2.astro"; +import SessionCard from "@components/SessionCard.astro"; // import CodeHeart from "@components/island/CodeHeart.svelte"; import Button from "@ui/Button.astro"; @@ -136,7 +137,18 @@ const nextSessionsOrdered = sameRoomNextSession
Duration:
{entry.data.duration} minutes
- +
+ + { + entry.data.youtube_url && ( + + Watch on YouTube ↗ + + ) + } +
@@ -231,44 +243,41 @@ const nextSessionsOrdered = sameRoomNextSession (nextSessionsInAllRooms?.length ?? 0) > 0 ? ( <> -
+ + )} {(nextSessionsInAllRooms?.length ?? 0) > 0 && ( -
- -

After this session

- -
-
+ <> + + +

+ After this session

+ + )}
@@ -279,10 +288,51 @@ const nextSessionsOrdered = sameRoomNextSession