diff --git a/src/components/island/BingoCard.svelte b/src/components/island/BingoCard.svelte index 23766338b..9b4963b0b 100644 --- a/src/components/island/BingoCard.svelte +++ b/src/components/island/BingoCard.svelte @@ -29,6 +29,8 @@ { year: 2026, city: 'KrakΓ³w' }, ]; + const COLS = 5; + function loadChecked() { if (typeof localStorage === 'undefined') return new Array(25).fill(false); try { @@ -39,9 +41,50 @@ } let checked = $state(loadChecked()); - let checkedCount = $derived(checked.filter(Boolean).length); + let bingo = $state(false); + let showResult = $state(false); + + // Trigger initial check after render + $effect(() => { + if (!bingo) checkBingo(); + }); + + function checkBingo() { + const lines = []; + + // rows + for (let r = 0; r < 5; r++) { + lines.push([0,1,2,3,4].map(c => r * 5 + c)); + } + // cols + for (let c = 0; c < 5; c++) { + lines.push([0,1,2,3,4].map(r => r * 5 + c)); + } + // diagonals + lines.push([0,6,12,18,24]); + lines.push([4,8,12,16,20]); + + for (const line of lines) { + if (line.every(i => checked[i])) { + if (!bingo) { + bingo = true; + try { localStorage.setItem(STORAGE_KEY, JSON.stringify([...checked])); } catch {} + setTimeout(() => { showResult = true; }, 600); + } + return; + } + } + } + + function reset() { + checked = new Array(25).fill(false); + bingo = false; + showResult = false; + try { localStorage.setItem(STORAGE_KEY, JSON.stringify([...checked])); } catch {} + } function toggle(i) { + if (bingo) return; checked[i] = !checked[i]; try { localStorage.setItem(STORAGE_KEY, JSON.stringify([...checked])); @@ -51,185 +94,173 @@ function buildShareText() { const attended = editions.filter((_, i) => checked[i]).map(e => `${e.year} ${e.city}`); const count = attended.length; - const base = `I've attended ${count} EuroPython conference${count !== 1 ? 's' : ''}! π`; - return count > 0 ? `${base}\n${attended.join(' Β· ')}` : base; + const base = `I just completed #EuroPythonBingo! π`; + return `${base}\n${count} editions attended\n${attended.join(' Β· ')}`; } - const PAGE_URL = 'https://ep2026.europython.eu/#bingo'; - const BINGO_PAGE_URL = 'https://ep2026.europython.eu/bingo'; + const PAGE_URL = 'https://ep2026.europython.eu/bingo'; function shareLinkedIn() { - window.open( - `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(BINGO_PAGE_URL)}`, - '_blank', 'noopener,noreferrer' - ); + const text = buildShareText() + '\n\n' + PAGE_URL; + window.open(`https://www.linkedin.com/feed/?shareActive=true&text=${encodeURIComponent(text)}`, '_blank', 'noopener,noreferrer'); } function shareX() { - const text = buildShareText() + '\n#EuroPython #Python'; - window.open( - `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(PAGE_URL)}`, - '_blank', 'noopener,noreferrer' - ); + const text = buildShareText(); + window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(PAGE_URL)}`, '_blank', 'noopener,noreferrer'); } function shareBlueSky() { - const text = `${buildShareText()}\n#EuroPython #Python\n${PAGE_URL}`; - window.open( - `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`, - '_blank', 'noopener,noreferrer' - ); + const text = `${buildShareText()}\n\n${PAGE_URL}`; + window.open(`https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`, '_blank', 'noopener,noreferrer'); } function shareMastodon() { - const text = `${buildShareText()}\n#EuroPython #Python\n${PAGE_URL}`; - window.open( - `https://shareopenly.org/share/?url=${encodeURIComponent(PAGE_URL)}&text=${encodeURIComponent(text)}`, - '_blank', 'noopener,noreferrer' - ); + const text = `${buildShareText()}\n\n${PAGE_URL}`; + window.open(`https://shareopenly.org/share/?url=${encodeURIComponent(PAGE_URL)}&text=${encodeURIComponent(text)}`, '_blank', 'noopener,noreferrer'); } - function downloadImage() { + function downloadSvg() { + const CELL = 124; + const GAP = 6; + const PAD = 20; + const HEADER_H = 74; + const FOOTER_H = 46; const COLS = 5; - const CELL = 130; - const PAD = 28; - const HEADER_H = 100; - const W = COLS * CELL + PAD * 2; - const H = COLS * CELL + PAD * 2 + HEADER_H; - - const canvas = document.createElement('canvas'); - canvas.width = W; - canvas.height = H; - const ctx = canvas.getContext('2d'); - - /* background */ - ctx.fillStyle = '#0b1121'; - ctx.fillRect(0, 0, W, H); - - /* title */ - ctx.fillStyle = '#f0c040'; - ctx.font = 'bold 30px system-ui, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText('EuroPython Bingo', W / 2, PAD + 34); - - ctx.fillStyle = 'rgba(255,255,255,0.4)'; - ctx.font = '13px system-ui, sans-serif'; - ctx.fillText( - `${checkedCount} of 25 editions attended Β· ep2026.europython.eu/#bingo`, - W / 2, PAD + 60 - ); + const ROWS = 5; + const W = COLS * CELL + (COLS - 1) * GAP + PAD * 2; + const H = HEADER_H + ROWS * CELL + (ROWS - 1) * GAP + FOOTER_H + PAD * 2; + + const isDark = document.documentElement.classList.contains('dark'); + const bgColor = isDark ? '#0b1121' : '#f5f0eb'; + const textColor = isDark ? '#ffffff' : '#1a1a2e'; + const accentColor = isDark ? '#f0c040' : '#1a56db'; + const mutedColor = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.4)'; + const cellBg = isDark ? '#0d1520' : '#ffffff'; + const cellBorder = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.1)'; + const flippedBg = isDark ? '#f0c040' : '#1a56db'; + const flippedText = isDark ? '#0b1121' : '#ffffff'; + const count = checked.filter(Boolean).length; + + let svg = ``; + + 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); }
- {checkedCount === 0 - ? "Click each edition youβve attended" - : checkedCount === 25 - ? 'π You attended all 25 editions!' - : `${checkedCount} of 25 attended`} -
- -+ {checked.filter(Boolean).length === 0 + ? "Flip cards for editions you've attended" + : `${checked.filter(Boolean).length} of 25 flipped`} +
+ {:else} +π BINGO! You completed a line!
+ {/if} + +