@tailwind base; @tailwind components; @tailwind utilities; @font-face { font-family: 'Manrope Variable'; font-style: normal; font-display: swap; font-weight: 200 800; src: url('@fontsource-variable/manrope/files/manrope-latin-wght-normal.woff2') format('woff2-variations'); } /* * PIG's token layer, with the accent inlined as literals rather than written * onto the root at runtime. This site has no user-chosen accent, so a runtime * theme module would be a moving part that buys nothing here. * * Every colour is an HSL channel triple — `H S% L%` — so Tailwind can compose * it with an opacity modifier (`bg-brand/10`). Writing `hsl(...)` into the * variable breaks that silently: the class compiles and the opacity is ignored. */ :root { --bg: 240 5% 98%; --surface: 0 0% 100%; --surface-2: 240 5% 97%; --border: 240 6% 90%; --fg: 240 10% 4%; --muted: 240 4% 46%; --accent: 262 83% 55%; --accent-fg: 263 70% 46%; --accent-on: 0 0% 100%; --accent-subtle: 270 100% 97%; --positive: 160 84% 24%; --warning: 32 95% 31%; --danger: 0 72% 45%; --info: 201 90% 32%; --shadow: 240 10% 4%; /* * Board tiles. Foregrounds are stated explicitly and measured, because the * board is the one place on the site where colour carries meaning: an unlit * tile and a grey tile differ only by fill, and a letter that fails contrast * against its own tile makes the result unreadable exactly when it matters. * * Measured against each tile's own fill: * exact white on 160 84% 24% -> 6.03:1 * present white on 32 95% 31% -> 5.03:1 * absent white on 240 4% 46% -> 4.61:1 */ --tile-exact: 160 84% 24%; --tile-exact-fg: 0 0% 100%; --tile-present: 32 95% 31%; --tile-present-fg: 0 0% 100%; --tile-absent: 240 4% 46%; --tile-absent-fg: 0 0% 100%; --dur-1: 120ms; --dur-2: 180ms; --dur-3: 240ms; --ease-out: cubic-bezier(0.2, 0, 0, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1); --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); --safe-left: env(safe-area-inset-left, 0px); --safe-right: env(safe-area-inset-right, 0px); --app-header-h: calc(3.5rem + var(--safe-top)); } @media (min-width: 1024px) { :root { --app-header-h: calc(4rem + var(--safe-top)); } } :root[data-theme='dark'] { --bg: 240 10% 4%; --surface: 240 8% 7%; --surface-2: 240 6% 11%; --border: 240 5% 18%; --fg: 0 0% 98%; --muted: 240 5% 60%; --accent: 258 90% 70%; --accent-fg: 255 92% 78%; --accent-on: 264 80% 12%; --accent-subtle: 260 35% 20%; --positive: 158 64% 48%; --warning: 38 92% 58%; --danger: 0 84% 65%; --info: 199 89% 58%; /* * Dark tiles lighten and take a dark glyph. Keeping the light theme's dark * fills would have made the board the only near-black object on a near-black * page, so a solved row read as an empty one. * exact 240 10% 4% on 158 64% 48% -> 7.31:1 * present 240 10% 4% on 38 92% 58% -> 9.24:1 * absent 0 0% 98% on 240 5% 28% -> 8.94:1 */ --tile-exact: 158 64% 48%; --tile-exact-fg: 240 10% 4%; --tile-present: 38 92% 58%; --tile-present-fg: 240 10% 4%; --tile-absent: 240 5% 28%; --tile-absent-fg: 0 0% 98%; } /* * High contrast. Green/yellow/grey is a colour-only distinction, which is * exactly why the original game ships a high-contrast mode. Toggled from the * header and persisted per-viewer; the tiles also carry a glyph in this mode, * so the result survives being printed, screenshotted or seen by someone with * deuteranopia. */ :root[data-contrast='high'] { --tile-exact: 209 100% 40%; --tile-present: 27 100% 42%; --tile-absent: 240 4% 38%; --tile-exact-fg: 0 0% 100%; --tile-present-fg: 0 0% 100%; --tile-absent-fg: 0 0% 100%; } @layer base { * { @apply border-border; } body { @apply bg-bg text-fg font-sans antialiased; /* Painted explicitly: the viewer's chrome shows through a transparent body and the page would borrow the host's theme instead of its own. */ text-rendering: optimizeLegibility; } /* Tabular figures everywhere a number is compared to another number. */ .nums { font-variant-numeric: tabular-nums; } :focus-visible { @apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-bg; } } @layer components { .card { @apply rounded-xl border border-border bg-surface; } /* 44px minimum touch target — the board keys and step chips are the two places this is load-bearing on a phone. */ .tap { @apply min-h-11 min-w-11; } } /* * Reduced motion clamps everything to effectively zero. That is correct, and * it is also why every state change in this app must ALSO be announced: with * the flip gone, colour alone carries the result, and colour alone is not a * result for a screen-reader user. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }