- {currentPath !== "/" && ( + {!hideHeader && currentPath !== "/" && ( { + const cellsParam = url.searchParams.get("cells") || ""; + const isDark = url.searchParams.get("dark") !== "0"; + + const checked = new Array(25).fill(false); + if (cellsParam) { + cellsParam + .split(",") + .map(Number) + .filter((n) => !isNaN(n) && n >= 0 && n < 25) + .forEach((i) => { + checked[i] = true; + }); + } + + const svg = generateBingoSvg({ checked, isDark }); + + const resvg = new Resvg(svg, { + fitTo: { + mode: "width", + value: SVG_W * 2, + }, + background: isDark ? "#0b1121" : "#f5f0eb", + }); + + const pngData = resvg.render(); + const pngBuffer = pngData.asPng(); + const body = new Uint8Array(pngBuffer); + + return new Response(body, { + status: 200, + headers: { + "Content-Type": "image/png", + "Cache-Control": "public, max-age=86400, s-maxage=86400", + }, + }); +}; diff --git a/src/pages/bingo/[slug].astro b/src/pages/bingo/[slug].astro new file mode 100644 index 000000000..dd2db3d11 --- /dev/null +++ b/src/pages/bingo/[slug].astro @@ -0,0 +1,212 @@ +--- +import { BINGO_LINES } from "@data/bingoCodes"; +import { EDITIONS, UNIQUE_CITIES, EDITION_COUNT } from "@data/editions"; + +export async function getStaticPaths() { + return BINGO_LINES.map((line) => ({ + params: { slug: line.code }, + props: { line }, + })); +} + +const { line } = Astro.props; +const winSet = new Set(line.cells); +--- + + + + + + + EuroPython 2026 Bingo — I completed {line.label}! + + + + + + + + + + + + + + + + + + + + + + +

EuroPython 2026 Bingo — I completed {line.label}!

+ +
+
+
EuroPython
Bingo
+

+ {EDITION_COUNT} editions · {UNIQUE_CITIES} cities · One community +

+

Completed {line.label}

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

+ Play your own card → +

+
+ + diff --git a/src/pages/bingo/index.astro b/src/pages/bingo/index.astro new file mode 100644 index 000000000..cff2f5daf --- /dev/null +++ b/src/pages/bingo/index.astro @@ -0,0 +1,125 @@ +--- +import BingoCard from "@components/island/BingoCard.svelte"; + +const cellsParam = Astro.url.searchParams.get("cells") || ""; +const ogImageUrl = cellsParam + ? new URL("/api/og-bingo.png?cells=" + cellsParam, Astro.url) + : null; +--- + + + + + + + EuroPython 2026 Bingo — Play your own card! + + + + + + {ogImageUrl && } + + + + + + + + + + + + + +

EuroPython 2026 Bingo

+ +
+
+
EuroPython
Bingo
+

+ 25 editions · 13 cities · One community +

+
+ + + + +
+ + diff --git a/src/pages/index.astro b/src/pages/index.astro index afdc9b09e..4e2757ab1 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -14,6 +14,7 @@ import Programme from "@sections/programme.astro"; import EuroSciPy from "@sections/euroscipy.astro"; import CommunityPartners from "@sections/community-partners.astro"; import Connect from "@sections/connect.astro"; +import Bingo from "@sections/bingo.astro"; --- + diff --git a/src/pages/robots.txt.ts b/src/pages/robots.txt.ts index 93c5aec32..d1ec3131f 100644 --- a/src/pages/robots.txt.ts +++ b/src/pages/robots.txt.ts @@ -1,6 +1,9 @@ import type { APIRoute } from "astro"; const previewRobots = ` +User-agent: LinkedInBot +Allow: / + User-agent: * Disallow: / `; @@ -15,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);