- {currentPath !== "/" && ( + {!hideHeader && currentPath !== "/" && ( - -
-
-

EuroPython
Bingo

-

- 25 editions. 13 cities. One community.
- How many have you attended? -

-
- - -
-
- - - diff --git a/src/pages/bingo/[slug].astro b/src/pages/bingo/[slug].astro index 07457d113..cf0cc35be 100644 --- a/src/pages/bingo/[slug].astro +++ b/src/pages/bingo/[slug].astro @@ -1,5 +1,6 @@ --- import { BINGO_LINES } from "@data/bingoCodes"; +import { EDITIONS, UNIQUE_CITIES, EDITION_COUNT } from "@data/editions"; export async function getStaticPaths() { return BINGO_LINES.map((line) => ({ @@ -9,160 +10,205 @@ export async function getStaticPaths() { } const { line } = Astro.props; - -const editions = [ - { year: 2002, city: "Charleroi" }, - { year: 2003, city: "Charleroi" }, - { year: 2004, city: "Gothenburg" }, - { year: 2005, city: "Gothenburg" }, - { year: 2006, city: "CERN, Geneva" }, - { year: 2007, city: "Vilnius" }, - { year: 2008, city: "Vilnius" }, - { year: 2009, city: "Birmingham" }, - { year: 2010, city: "Birmingham" }, - { year: 2011, city: "Florence" }, - { year: 2012, city: "Florence" }, - { year: 2013, city: "Florence" }, - { year: 2014, city: "Berlin" }, - { year: 2015, city: "Bilbao" }, - { year: 2016, city: "Bilbao" }, - { year: 2017, city: "Rimini" }, - { year: 2018, city: "Edinburgh" }, - { year: 2019, city: "Basel" }, - { year: 2020, city: "Online" }, - { year: 2021, city: "Online" }, - { year: 2022, city: "Dublin" }, - { year: 2023, city: "Prague" }, - { year: 2024, city: "Prague" }, - { year: 2025, city: "Prague" }, - { year: 2026, city: "KrakΓ³w" }, -]; - const winSet = new Set(line.cells); --- - - - - EuroPython 2026 Bingo β€” I completed {line.label}! - - - - - - - - - - - - - - - - - + .bingo-cta a { + display: inline-block; + color: var(--color-accent); + font-size: 0.95rem; + font-weight: 600; + text-decoration: underline; + text-underline-offset: 3px; + transition: opacity 0.15s; + } + .bingo-cta a:hover { + opacity: 0.7; + } + -

EuroPython 2026 Bingo β€” I completed {line.label}!

-
-

EuroPython Bingo

-

Completed {line.label}

-
- {editions.map((ed, i) => ( -
- {ed.year} - {ed.city} -
- ))} +

EuroPython 2026 Bingo β€” I completed {line.label}!

+ +
diff --git a/src/pages/robots.txt.ts b/src/pages/robots.txt.ts index 3ac3a50d4..d1ec3131f 100644 --- a/src/pages/robots.txt.ts +++ b/src/pages/robots.txt.ts @@ -18,6 +18,7 @@ Allow: / Sitemap: https://ep2026.europython.eu/sitemap-index.xml `; -const isPreview = import.meta.env.MODE == "preview"; +const envMode = import.meta.env?.MODE ?? "production"; +const isPreview = envMode == "preview"; export const GET: APIRoute = () => new Response(isPreview ? previewRobots : prodRobots); From 449af9cc1b434b28783f93b15921d0bb1b1501e3 Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 25 Jun 2026 00:51:57 +0200 Subject: [PATCH 6/8] Bingo cleanup --- src/pages/bingo/[slug].astro | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/bingo/[slug].astro b/src/pages/bingo/[slug].astro index cf0cc35be..dd2db3d11 100644 --- a/src/pages/bingo/[slug].astro +++ b/src/pages/bingo/[slug].astro @@ -59,9 +59,7 @@ const winSet = new Set(line.cells); } * { margin: 0; padding: 0; box-sizing: border-box; } - body { body { background: var(--color-bg-dark); min-height: 100vh; font-family: system-ui, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; color: var(--color-text-primary); } - } .bingo-page { width: 100%; From b0de4a07b873eef1889440b40e88559635c6bfe2 Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 25 Jun 2026 17:06:49 +0200 Subject: [PATCH 7/8] Bingo v2 --- src/components/island/BingoCard.svelte | 102 +++++++++++++++------ src/pages/bingo/index.astro | 117 +++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 29 deletions(-) create mode 100644 src/pages/bingo/index.astro diff --git a/src/components/island/BingoCard.svelte b/src/components/island/BingoCard.svelte index 81e229abb..de802f6bc 100644 --- a/src/components/island/BingoCard.svelte +++ b/src/components/island/BingoCard.svelte @@ -9,7 +9,21 @@ if (typeof localStorage === 'undefined') return new Array(25).fill(false); try { const saved = localStorage.getItem(STORAGE_KEY); - if (saved) return JSON.parse(saved); + let arr = saved ? JSON.parse(saved) : new Array(25).fill(false); + + // Restore card state from URL hash (e.g. /bingo#0,5,12,17) + if (typeof window !== 'undefined' && window.location.hash.length > 1) { + const indices = window.location.hash + .slice(1) + .split(',') + .map(Number) + .filter((n) => !isNaN(n) && n >= 0 && n < 25); + indices.forEach((i) => { + arr[i] = true; + }); + } + + return arr; } catch {} return new Array(25).fill(false); } @@ -68,7 +82,9 @@ function buildShareText() { const attended = editions.filter((_, i) => checked[i]).map(e => `${e.year} ${e.city}`); const count = attended.length; - const base = `I just completed #EuroPythonBingo! 🐍`; + const base = bingo + ? `I just completed #EuroPythonBingo! 🐍` + : `I'm playing #EuroPythonBingo! 🐍`; return `${base}\n${count} editions attended\n${attended.join(' · ')}`; } @@ -99,10 +115,15 @@ return ''; } + function getShareHash() { + const flipped = checked.map((v, i) => v ? i : -1).filter(i => i >= 0); + return flipped.join(','); + } + let bingoCode = $derived(bingo ? getBingoCode() : ''); let shareUrl = $derived(bingoCode ? window.location.origin + '/bingo/' + bingoCode - : window.location.origin + '/bingo'); + : window.location.origin + '/bingo#' + getShareHash()); function shareLinkedIn() { const text = buildShareText() + '\n\n' + shareUrl; @@ -124,7 +145,7 @@ window.open(`https://shareopenly.org/share/?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(text)}`, '_blank', 'noopener,noreferrer'); } - function downloadSvg() { + function downloadPng() { const CELL = 124; const GAP = 6; const PAD = 20; @@ -169,7 +190,6 @@ const y = HEADER_H + row * (CELL + GAP); const flip = checked[i]; - // card background svg += ``; if (flip) { @@ -181,17 +201,32 @@ } }); - // watermark svg += `EuroPython 2026`; svg += ``; + // Render SVG to canvas and save as PNG const blob = new Blob([svg], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'europython-bingo.svg'; - a.click(); - URL.revokeObjectURL(url); + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + URL.revokeObjectURL(url); + canvas.toBlob((pngBlob) => { + const a = document.createElement('a'); + a.href = URL.createObjectURL(pngBlob); + a.download = 'europython-bingo.png'; + a.click(); + }); + }; + img.onerror = () => { + URL.revokeObjectURL(url); + console.error('Failed to render bingo PNG'); + }; + img.src = url; } @@ -236,28 +271,31 @@

πŸŽ‰ BINGO!

You completed a line!

- -