diff --git a/src/app/print/page.tsx b/src/app/print/page.tsx
new file mode 100644
index 0000000..377e409
--- /dev/null
+++ b/src/app/print/page.tsx
@@ -0,0 +1,188 @@
+import { prisma } from '@/lib/prisma';
+import { PrintTrigger } from '@/components/PrintTrigger';
+
+export const dynamic = 'force-dynamic';
+
+export default async function PrintPage() {
+ const settings = await prisma.gameSettings.findUnique({
+ where: { id: 'singleton' },
+ });
+
+ const squares = await prisma.square.findMany({
+ include: { user: { select: { name: true } } },
+ orderBy: { position: 'asc' },
+ });
+
+ const gridNumbers = await prisma.gridNumber.findMany({
+ orderBy: { position: 'asc' },
+ });
+
+ const hasNumbers = gridNumbers.length === 10;
+ const nfcNumbers = hasNumbers ? gridNumbers.map((g) => g.nfcNumber) : Array(10).fill('?');
+ const afcNumbers = hasNumbers ? gridNumbers.map((g) => g.afcNumber) : Array(10).fill('?');
+
+ // Build 10x10 grid
+ const grid: { name: string | null; confirmed: boolean }[][] = [];
+ for (let row = 0; row < 10; row++) {
+ grid[row] = [];
+ for (let col = 0; col < 10; col++) {
+ const sq = squares.find((s) => s.position === `${row}${col}`);
+ const rawName = sq?.user?.name || sq?.guestName || null;
+ // First name only to fit in cell
+ const name = rawName ? rawName.split(' ')[0] : null;
+ grid[row][col] = { name, confirmed: sq?.confirmed ?? false };
+ }
+ }
+
+ const nfcTeam = settings?.nfcTeam || 'NFC';
+ const afcTeam = settings?.afcTeam || 'AFC';
+ const nfcLogo = settings?.nfcLogo || null;
+ const afcLogo = settings?.afcLogo || null;
+ const sbLogo = settings?.sbLogo || null;
+ const eventName = settings?.eventName || 'Super Bowl Squares';
+ const eventDate = settings?.eventDate || '';
+
+ const CELL = 62; // px per cell
+ const NUM_CELL = 28; // px for number header/side column
+ const LOGO = 48;
+
+ return (
+ <>
+
+
+
+
+ {/* Header */}
+
+ {/* AFC team */}
+
+ {afcLogo && (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ )}
+
{afcTeam}
+
+
+ {/* Centre: SB logo + title */}
+
+ {sbLogo && (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ )}
+
{eventName}
+ {eventDate &&
{eventDate}
}
+
+
+ {/* NFC team */}
+
+
{nfcTeam}
+ {nfcLogo && (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ )}
+
+
+
+ {/* Grid */}
+
+ {/* NFC vertical label + number column */}
+
+ {/* Top-left spacer */}
+
+
+ {nfcTeam}
+
+
+ {/* NFC number cells */}
+ {nfcNumbers.map((n, i) => (
+
+ {n}
+
+ ))}
+
+
+ {/* Main grid area */}
+
+ {/* AFC label + number row */}
+
+ {afcNumbers.map((n, i) => (
+
+ {n}
+
+ ))}
+
+ {afcTeam}
+
+
+
+ {/* Square rows */}
+ {grid.map((row, rowIdx) => (
+
+ {row.map((cell, colIdx) => {
+ const taken = !!cell.name;
+ const bg = taken
+ ? cell.confirmed ? '#d1fae5' : '#fef9c3'
+ : '#f9fafb';
+ const textColor = taken ? '#111' : '#ccc';
+ return (
+
+ {cell.name || `${rowIdx}${colIdx}`}
+
+ );
+ })}
+
+ ))}
+
+
+
+ {/* Legend */}
+
+
+ >
+ );
+}
diff --git a/src/components/PrintTrigger.tsx b/src/components/PrintTrigger.tsx
new file mode 100644
index 0000000..8787809
--- /dev/null
+++ b/src/components/PrintTrigger.tsx
@@ -0,0 +1,11 @@
+'use client';
+
+import { useEffect } from 'react';
+
+export function PrintTrigger() {
+ useEffect(() => {
+ window.print();
+ }, []);
+
+ return null;
+}
diff --git a/src/components/grid/GridHeader.tsx b/src/components/grid/GridHeader.tsx
index 199b832..93b3da1 100644
--- a/src/components/grid/GridHeader.tsx
+++ b/src/components/grid/GridHeader.tsx
@@ -47,6 +47,14 @@ export function GridHeader({ settings }: GridHeaderProps) {