From 6739c46e592d55fbd9a485fcf39d4648f977cf94 Mon Sep 17 00:00:00 2001 From: Daria Grudzien Date: Tue, 23 Jun 2026 14:29:21 -0700 Subject: [PATCH] Flipcards --- src/components/island/BingoCard.svelte | 558 +++++++++++++++---------- 1 file changed, 344 insertions(+), 214 deletions(-) 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 = ` + + + + + EuroPython Bingo + ${count} of 25 editions`; editions.forEach((ed, i) => { const col = i % COLS; const row = Math.floor(i / COLS); - const x = PAD + col * CELL; - const y = HEADER_H + PAD + row * CELL; - const isChecked = checked[i]; - const isCurrent = ed.year === 2026; - - /* cell bg */ - ctx.fillStyle = isChecked ? '#f0c040' : isCurrent ? '#111d36' : '#0d1520'; - roundRect(ctx, x + 3, y + 3, CELL - 6, CELL - 6, 2); - ctx.fill(); - - /* cell border β€” dashed approximation via dotted segments */ - ctx.strokeStyle = isChecked ? '#d4a830' : isCurrent ? '#2a4a80' : 'rgba(255,255,255,0.12)'; - ctx.lineWidth = 1; - ctx.setLineDash([4, 4]); - roundRect(ctx, x + 3, y + 3, CELL - 6, CELL - 6, 2); - ctx.stroke(); - ctx.setLineDash([]); - - /* year */ - ctx.fillStyle = isChecked ? '#0b1121' : '#ffffff'; - ctx.font = `bold 21px system-ui, sans-serif`; - ctx.textAlign = 'center'; - ctx.fillText(ed.year.toString(), x + CELL / 2, y + CELL * 0.46); - - /* city */ - ctx.fillStyle = isChecked ? 'rgba(11,17,33,0.65)' : 'rgba(255,255,255,0.55)'; - ctx.font = `12px system-ui, sans-serif`; - ctx.fillText(ed.city, x + CELL / 2, y + CELL * 0.68); + const x = PAD + col * (CELL + GAP); + const y = HEADER_H + row * (CELL + GAP); + const flip = checked[i]; + + // card background + svg += ``; + + if (flip) { + svg += `${ed.year}`; + svg += `${ed.city}`; + } else { + svg += `${ed.city}`; + svg += `${ed.year}`; + } }); - const link = document.createElement('a'); - link.download = 'europython-bingo.png'; - link.href = canvas.toDataURL('image/png'); - link.click(); - } - - function roundRect(ctx, x, y, w, h, r) { - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.lineTo(x + w - r, y); - ctx.quadraticCurveTo(x + w, y, x + w, y + r); - ctx.lineTo(x + w, y + h - r); - ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); - ctx.lineTo(x + r, y + h); - ctx.quadraticCurveTo(x, y + h, x, y + h - r); - ctx.lineTo(x, y + r); - ctx.quadraticCurveTo(x, y, x + r, y); - ctx.closePath(); + // watermark + svg += `EuroPython 2026`; + 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`} -

- -
- {#each editions as edition, i} - - {/each} -
+ {#if !bingo} +

+ {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} + +
+
+
+ {#each editions as edition, i} + + {/each} +
+
-