/*
 * NiCok Games — public styles.
 *
 * Scoped under .nfcg-public so nothing leaks into the theme, and written
 * against Divi's habits rather than a block theme's: Divi styles every link
 * globally (#2ea3f2), so anything with its own link colour restates it rather
 * than inheriting.
 *
 * Layout leans on flex-wrap and aspect-ratio rather than media queries, so the
 * board is square and the sidebar drops below it purely on available width.
 */

.nfcg-public {
	--nfcg-indigo: #6d28d9;
	--nfcg-indigo-dark: #5b21b6;
	--nfcg-ink: #1a1130;
	--nfcg-muted: #6b6480;
	--nfcg-line: #e5e0f5;
	--nfcg-surface: #fff;
	--nfcg-surface-alt: #faf9ff;
	--nfcg-light: #efe8dc;
	--nfcg-dark: #b58863;
	--nfcg-accent: #f97316;

	/* Word Duel premium squares. Declared here rather than inline on the
	   square rules because the legend draws its swatches from the same five
	   values, and two hand-kept lists of colours drift the first time one is
	   adjusted — silently, since nothing would look broken, only wrong. */
	--nfcg-wd-plain: #f3efe6;
	--nfcg-wd-dl: #bfdbfe;
	--nfcg-wd-tl: #60a5fa;
	--nfcg-wd-tl-ink: #fff;
	--nfcg-wd-dw: #fbcfe8;
	--nfcg-wd-tw: #f97316;
	--nfcg-wd-tw-ink: #fff;
	--nfcg-wd-start: var(--nfcg-indigo);
	--nfcg-wd-start-ink: #fff;

	/* Presence. The green is the same #16a34a as tournament-manager's
	   .tm-podium__status-dot, so the two plugins agree on what "live" looks
	   like. The grey is a deliberate step darker than --nfcg-line, which is a
	   hairline colour and too pale to read as a state. */
	--nfcg-online: #16a34a;
	--nfcg-offline: #c9c3d9;
	--nfcg-display: "Sora", system-ui, -apple-system, sans-serif;
	--nfcg-body: "Inter", system-ui, -apple-system, sans-serif;

	color: var(--nfcg-ink);
	font-family: var(--nfcg-body);
	line-height: 1.5;
}

/* Themes reset every heading with an element-only selector — Divi ships
   `h1..h6 { color:#333; line-height:1em; padding-bottom:10px }` (verified in
   the page, not assumed). An element selector beats an *inherited* value
   outright, because inheritance carries no specificity at all, so the 1.5
   above never reaches a heading and the stray 10px silently grows any block
   measured off one.

   Split in two on purpose. Face and colour keep this rule's original 0-1-1,
   so headings that set their own colour lower down (.nfcg-moves__title is a
   muted h3) still lose to it exactly as before — widening h2/h3 to h1..h6 is
   the only intended change here.

   The leading and the padding are a *reset*, so they go in a `:where()` rule
   below at 0-1-0: no specificity of its own, comfortably above Divi's 0-0-1,
   but below any component rule further down this file. A reset is a floor, not
   a ceiling — at 0-1-1 it would outrank .nfcg-game-card__name and make that
   line-height dead code. The one Divi heading rule that outranks 0-1-0 is
   `body #main-content .et_builder_inner_content > h1..h6`, and its child
   combinator cannot reach a heading nested inside a card (verified). */
.nfcg-public :is(h1, h2, h3, h4, h5, h6) {
	font-family: var(--nfcg-display);
	color: var(--nfcg-ink);
}

.nfcg-public :where(h1, h2, h3, h4, h5, h6) {
	line-height: 1.25;
	padding-bottom: 0;
}

/* Divi colours every link; restate ours rather than inherit. */
.nfcg-public .nfcg-link,
.nfcg-public a.nfcg-link {
	color: var(--nfcg-indigo);
	font-weight: 600;
	text-decoration: none;
}

.nfcg-public .nfcg-link:hover {
	color: var(--nfcg-indigo-dark);
	text-decoration: underline;
}

/* ---------------------------------------------------------------- the match */

.nfcg-match__head {
	margin-bottom: 1.25rem;
}

.nfcg-match__title {
	margin: 0 0 0.75rem;
	font-size: 1.5rem;
	font-weight: 700;
}

.nfcg-match__status {
	margin: 0.5rem 0 0;
	color: var(--nfcg-muted);
	font-size: 0.95rem;
}

.nfcg-match__body {
	display: flex;
	flex-wrap: wrap;
	gap: 1.5rem;
	align-items: flex-start;
}

/* The board's column: a capture tray, the board, another capture tray.

   The sizing lives HERE rather than on .nfcg-board because .nfcg-match__body is
   a row flex — trays as bare siblings of the board would lay out beside it. */
.nfcg-board-stack {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	flex: 1 1 20rem;
	max-width: 34rem;
	min-width: 0;
}

.nfcg-board {

	/* The curtain a finished match throws over the board anchors to this, and
	   so does the piece that slides across it. */
	position: relative;
}

/* Reserves its own height whether or not anything has been taken, so the board
   does not jump down the page on the first capture. */
.nfcg-tray {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.15rem;
	min-height: 1.5rem;
}

/* How far ahead the material is, for the side that is ahead. Tabular so it
   does not jitter as it changes. */
.nfcg-tray__edge {
	margin-inline-start: 0.3rem;
	font-size: 0.8rem;
	font-variant-numeric: tabular-nums;
	opacity: 0.75;
}

.nfcg-match__side {
	flex: 1 1 14rem;
	min-width: 0;
}

/* ------------------------------------------------------- the spectator view

   Someone who is not one of the two players wants the board, not the working
   notation the players use. So the side column stops being a column: it drops
   below the board and the whole thing centres.

   The aside STAYS — it carries "Only members can play → Join NiCok", and a
   logged-out visitor watching a good game is exactly who that is for. It also
   holds the live notice (reconnecting, errors), which a spectator still needs.
   Only the move list goes. */

.nfcg-match--spectator .nfcg-match__body {
	flex-direction: column;
	align-items: center;
}

.nfcg-match--spectator .nfcg-board-stack {
	flex: 0 1 auto;
	width: 100%;
	margin-inline: auto;
}

.nfcg-match--spectator .nfcg-match__side {
	flex: 0 1 auto;
	width: 100%;
	max-width: 34rem;
	text-align: center;
}

/* Hidden rather than omitted, so a watching member who takes the empty seat
   gets it back without a reload. */
.nfcg-match--spectator .nfcg-moves,
.nfcg-match--spectator .nfcg-moves__title {
	display: none;
}

/* ---------------------------------------------------------------- the board */

.nfcg-board__grid {
	display: grid;

	/* Set by the client from the engine's own `size`, so one grid draws an
	   8x8 chess board and a 15x15 word board. Defaults to 8 so a stylesheet
	   loaded before any JS still lays out sensibly. */
	--nfcg-board-size: 8;
	grid-template-columns: repeat(var(--nfcg-board-size), 1fr);

	/* ROWS MUST BE EXPLICIT TOO. Without this they are implicit tracks sized
	   to their content, so a rank holding pieces grew taller than an empty
	   one — measured at 84px against 52px on a board whose squares were 68px
	   wide. The columns alone kept the *width* honest, which is why the board
	   looked plausible until you counted. */
	grid-template-rows: repeat(var(--nfcg-board-size), 1fr);

	/* Square whatever the column width resolves to, with no JS involved. */
	aspect-ratio: 1 / 1;
	width: 100%;
	border: 2px solid var(--nfcg-ink);
	border-radius: 10px;
	overflow: hidden;

	/* Makes cqw below resolve against the board rather than the viewport, so
	   the pieces scale with the board even inside a narrow Divi column. */
	container-type: inline-size;
}

.nfcg-square {
	display: flex;
	align-items: center;
	justify-content: center;

	/* Anchors the .is-target dot. */
	position: relative;
	padding: 0;
	margin: 0;
	border: 0;
	border-radius: 0;
	cursor: pointer;

	/* AND THIS IS THE HALF THAT ACTUALLY BINDS. A grid item's automatic
	   minimum size is its content, so `1fr` is only a maximum — a piece taller
	   than its share still pushed the track open. Zeroing both minimums makes
	   the track the authority, and `overflow` keeps anything oversized inside
	   its own square instead of over its neighbour. Applies to every engine,
	   so a future board gets it without asking. */
	min-width: 0;
	min-height: 0;
	overflow: hidden;

	/* The glyphs are text, so they scale with the board rather than needing
	   a sprite sheet or 12 image requests. */
	font-size: clamp(1.25rem, 6cqw, 2.5rem);
	line-height: 1;
	transition: box-shadow 120ms ease;
}

/* ------------------------------------------------------------ board themes

   A theme redefines exactly two variables. Those two were already the only
   thing deciding a square's colour, so this is the whole feature — no per-theme
   square rules, and a sixth theme is one block here plus one slug in
   NFCG_Constants::BOARD_THEMES.

   The class lands on the match root from the template, so the first paint is
   already themed rather than repainting under the reader. `walnut` restates
   the defaults rather than relying on them, so the picker reads honestly: every
   theme sets its own colours and none of them is "whatever was there". */

.nfcg-board-theme--walnut {
	--nfcg-light: #efe8dc;
	--nfcg-dark: #b58863;
}

.nfcg-board-theme--forest {
	--nfcg-light: #eeeed2;
	--nfcg-dark: #769656;
}

.nfcg-board-theme--ocean {
	--nfcg-light: #e3ecf4;
	--nfcg-dark: #5b7f9e;
}

.nfcg-board-theme--indigo {
	--nfcg-light: #eeeaf7;
	--nfcg-dark: #7c6bb0;
}

.nfcg-board-theme--slate {
	--nfcg-light: #e9e9ec;
	--nfcg-dark: #7d8595;
}

.nfcg-square.is-light {
	background: var(--nfcg-light);
}

.nfcg-square.is-dark {
	background: var(--nfcg-dark);
}

.nfcg-square.is-selected {
	box-shadow: inset 0 0 0 3px var(--nfcg-indigo);
}

/* ------------------------------------------------------------ chess pieces

   Six <symbol>s in the page (match-board.php), coloured here from `data-piece`
   — uppercase is white, lowercase black — so one shape serves both sides.

   `fill` and `stroke` inherit into the symbol's contents through <use>, which
   is the whole reason the shapes carry no colour of their own. */

.nfcg-piece {
	width: 84%;
	height: 84%;
	fill: var(--nfcg-piece-fill, #f4f1ea);
	stroke: var(--nfcg-piece-stroke, #2b2b2b);

	/* Must stay under the gaps left between the shapes in the sprite (0.6 of
	   the 45-unit viewBox). A fatter stroke closes them, and the black pieces
	   lose the seams that separate crown from collar from base. */
	stroke-width: 1;
	stroke-linejoin: round;
	stroke-linecap: round;

	/* Lifts the piece off the square just enough to read on either colour,
	   which a flat silhouette does not do on a mid-tone board. */
	filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.28));

	/* The square owns the click. Without this, a click that lands on the
	   artwork has the <svg> as its target and the handler's dataset lookup
	   finds nothing. */
	pointer-events: none;
}

/* A tray piece is a small fixed size, not 84% of a square.

   MUST come after .nfcg-piece: same specificity, so source order is what
   decides, and declared before it this lost and the piece grew to fill the
   tray — measured at 301px tall. */
.nfcg-piece--tray {
	width: 1.15rem;
	height: 1.15rem;

	/* No drop shadow at this size — it only muddies the silhouette. */
	filter: none;
}

/* Detail that reads as ink rather than as an outlined shape: the knight's eye
   and nostril, the bishop's slit. Filled with the outline colour and given no
   stroke of its own, so it stays crisp when the board is small. */
.nfcg-piece__ink {
	fill: var(--nfcg-piece-stroke, #2b2b2b);
	stroke: none;
}

/* White. Not pure #fff — that flares against a light square and loses its own
   outline at small sizes.

   Matched on data-piece alone rather than on .nfcg-square, so the rule reaches
   the <svg> as well as the square it sits in. That is what lets the flyer clone
   keep its colour once it is parented to .nfcg-board and no longer inside a
   square — without it a black king slides across the board white, because the
   var() fallback below is the light colour. */
[data-piece="K"],
[data-piece="Q"],
[data-piece="R"],
[data-piece="B"],
[data-piece="N"],
[data-piece="P"] {
	--nfcg-piece-fill: #f7f4ee;
	--nfcg-piece-stroke: #2b2723;
}

/* Black. Dark on dark, so there is no pale halo around the silhouette — the
   seams between crown, collar, step and plinth come from real gaps left
   between the shapes, which show the square through and so read light on a
   light square exactly as a carved set does. That only works while the stroke
   stays thinner than the gaps; see the stroke-width note above. */
[data-piece="k"],
[data-piece="q"],
[data-piece="r"],
[data-piece="b"],
[data-piece="n"],
[data-piece="p"] {
	--nfcg-piece-fill: #2c2724;
	--nfcg-piece-stroke: #17140f;
}

/* A dot rather than a full highlight: it marks the square without hiding
   whatever is standing on it, which matters for captures. */
.nfcg-square.is-target::after {
	content: "";
	position: absolute;
	width: 28%;
	height: 28%;
	border-radius: 50%;
	background: rgba(109, 40, 217, 0.55);

	/* Above the CSS-drawn checkers disc. A landing square is always empty in
	   checkers, so this only matters if a future engine says otherwise — but
	   the stacking order is cheaper to state than to rediscover. */
	z-index: 1;
}

/* Checkers pieces are drawn, not typed.
   Every candidate glyph renders far too small against a board square, and
   depends on font coverage we cannot verify — the same trap the replay
   controls fell into. A disc is a border-radius, so it scales exactly with
   the board and cannot go missing.

   A REAL ELEMENT, not a ::before, and keyed off data-piece ON ITSELF rather
   than on the square around it. Both halves are what make the piece animatable:
   a pseudo-element cannot be cloned into the flyer that slides a move across
   the board, and a clone parented outside its square would match none of these
   rules and fly with no colour at all. */
.nfcg-disc {
	width: 72%;
	aspect-ratio: 1;
	border-radius: 50%;
	box-shadow:
		inset 0 -2px 3px rgba(0, 0, 0, 0.28),
		0 1px 2px rgba(0, 0, 0, 0.35);

	/* The square owns the click, exactly as for .nfcg-piece. */
	pointer-events: none;
}

.nfcg-disc[data-piece="r"],
.nfcg-disc[data-piece="R"] {
	background: #c0392b;
	border: 2px solid #8e2a20;
}

.nfcg-disc[data-piece="w"],
.nfcg-disc[data-piece="W"] {
	background: #f7f3ec;
	border: 2px solid #8b7355;
}

/* A crown, as a ring inside the disc. Kings have to be tellable apart at a
   glance or a player walks a man into one. */
.nfcg-disc[data-piece="R"] {
	box-shadow:
		inset 0 0 0 3px #f4d58d,
		inset 0 -2px 3px rgba(0, 0, 0, 0.28),
		0 1px 2px rgba(0, 0, 0, 0.35);
}

.nfcg-disc[data-piece="W"] {
	box-shadow:
		inset 0 0 0 3px #6d28d9,
		inset 0 -2px 3px rgba(0, 0, 0, 0.2),
		0 1px 2px rgba(0, 0, 0, 0.35);
}


/* The piece in flight.

   A move used to appear: the grid is rebuilt wholesale on every render, so a
   piece simply stopped being on one square and started being on another. This
   is a clone of it, parented to .nfcg-board rather than to the grid — the grid
   is overflow:hidden and would clip anything crossing it — and positioned from
   the squares' own offsets, so it needs no knowledge of the board's geometry.

   z-index clears the .is-target dot at 1. It never meets the curtain, which is
   only raised on a match ending. */
.nfcg-flyer {
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	pointer-events: none;
	z-index: 2;
}

.nfcg-square:focus-visible {
	outline: 3px solid var(--nfcg-accent);
	outline-offset: -3px;
}

/* ---------------------------------------------------------------- the seats */

.nfcg-seats {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.nfcg-seat {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	flex: 1 1 10rem;
	padding: 0.6rem 0.85rem;
	border: 1px solid var(--nfcg-line);
	border-radius: 12px;
	background: var(--nfcg-surface-alt);

	/* The presence dot anchors to this. */
	position: relative;
}

/* ------------------------------------------------------------- presence

   Is this player actually at the board? A corner badge rather than a dot
   inline with the name: .nfcg-seat is a column flex, so an inline dot would
   need a row wrapper around the name — new markup in both the template and
   renderSeats(), which have to stay identical — and could reflow the card.
   This is one absolutely-positioned child that cannot move anything.

   No collision with .nfcg-seat__result: presence is suppressed once a match
   is terminal, which is the only time a result badge exists.

   Deliberately not animated. It would be this file's first @keyframes, and a
   pulse buys nothing when the dot is the only green thing on the page —
   tournament-manager's own status dot does not pulse either. */
.nfcg-seat__presence {
	position: absolute;
	top: 0.55rem;
	right: 0.6rem;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--nfcg-offline);
}

.nfcg-seat__presence.is-online {
	background: var(--nfcg-online);
}

/* The dot is decorative (aria-hidden); this carries the same fact as text,
   because a colour on its own is not a status. No such utility exists in this
   repo and .screen-reader-text belongs to the theme, so it is defined here
   rather than borrowed from something that may not be loaded. */
.nfcg-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.nfcg-seat.is-turn {
	border-color: var(--nfcg-indigo);
	box-shadow: 0 0 0 2px rgba(109, 40, 217, 0.15);
}

.nfcg-seat.is-you .nfcg-seat__name::after {
	content: " (you)";
	color: var(--nfcg-muted);
	font-weight: 400;
}

/* ------------------------------------------------ before kick-off (pending)

   Both players are seated and the match is technically `ready`, but the
   appointed time has not arrived. The seats are shown greyed rather than
   hidden: who is playing is the most interesting thing on the page before
   the game starts, it just should not look like anybody is on the clock.

   The turn highlight is suppressed by not emitting `is-turn` at all (the
   template and public.js both check), so there is nothing here to undo. */

.nfcg-match--pending .nfcg-seat {
	opacity: 0.55;
	background: var(--nfcg-surface);
}

/* The board is drawn but not yours to touch yet.

   `your_turn` is already false before kick-off, so a click is refused by the
   handler and nothing can happen — but the cells are still <button>s, and 64
   of them offering a pointer cursor is an invitation to try. pointer-events
   removes the affordance rather than only the effect. Nothing inside the grid
   is interactive before kick-off, so there is nothing to lose. */
.nfcg-match--pending .nfcg-board__grid {
	opacity: 0.7;
	filter: saturate(0.6);
	pointer-events: none;
}


/* The clock. Tabular figures are not a nicety here: proportional digits change
   width as they change value, so an un-tabular clock jitters its neighbours
   sideways once a second for the whole game. */
.nfcg-seat__clock {
	font-family: var(--nfcg-display);
	font-size: 1.05rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum" 1;
	color: var(--nfcg-ink);
	line-height: 1;
}

/* Word Duel past the bank: the same clock, counting up, and costing points by
   the minute. It has to look like a different state rather than a smaller
   number — a player glancing at "+0:12" must not read it as twelve seconds
   left. */
.nfcg-seat__clock.is-overtime {
	color: var(--nfcg-accent);
}

/* Only the seat actually spending time gets the emphasis, so which clock is
   running is legible without watching it for a second to see which one moves. */
.nfcg-seat:not(.is-turn) .nfcg-seat__clock {
	opacity: 0.55;
}

.nfcg-seat__penalty {
	font-size: 0.75rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--nfcg-accent);
}

.nfcg-seat__label {
	color: var(--nfcg-muted);
	font-size: 0.75rem;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.nfcg-seat__name {
	font-family: var(--nfcg-display);
	font-weight: 600;
}

.nfcg-seat__result {
	align-self: flex-start;
	padding: 0.1rem 0.5rem;
	border-radius: 999px;
	font-size: 0.7rem;
	font-weight: 700;
	text-transform: uppercase;
}

.nfcg-seat__result--win {
	background: #dcfce7;
	color: #166534;
}

.nfcg-seat__result--loss {
	background: #fee2e2;
	color: #991b1b;
}

.nfcg-seat__result--draw {
	background: #e5e0f5;
	color: #4c1d95;
}

/* Presence is suppressed on a terminal match (see presence_visible_to), so the
   seat row has nothing live left to say and the result carries it. */
.nfcg-match--over .nfcg-seat {
	background: var(--nfcg-surface);
}

/* ------------------------------------------------------------ the result

   A decided game used to look like a live one: the only difference was a small
   badge beside each name, because nothing on the surface knew the match was
   over. `nfcg-match--over` is set by match-board.php and by public.js from the
   same fact, exactly like --pending and --spectator.

   The board keeps its colours — a finished game is the thing people come back
   to look at and share, so muting it the way --pending does would be wrong. It
   loses only its interactivity, which is already refused server-side; this
   removes the pointer cursor that invites the click. */
.nfcg-match--over .nfcg-board__grid {
	pointer-events: none;
}

/* The sentence is now the most important thing on the surface, so it stops
   being muted secondary text and reads as the headline it is — and it gets a
   surface to sit on, so the eye lands there before the badges. */
.nfcg-match--over .nfcg-match__status {
	color: var(--nfcg-ink);
	font-family: var(--nfcg-display);
	font-size: 1.05rem;
	font-weight: 600;
	margin-top: 0.9rem;
	padding: 0.8rem 1.05rem;
	border-radius: 10px;
	background: #f4f0ff;
	box-shadow: inset 0 0 0 1px #ddd2fa;
}

/* A draw is not a win in a quieter shade — it is a different result, so it
   takes the draw badge's own lavender rather than the winner's indigo. Read
   off the badge with :has(), so nothing new has to be emitted to say it. */
.nfcg-match--over:has(.nfcg-seat__result--draw) .nfcg-match__status {
	background: var(--nfcg-surface-alt);
	box-shadow: inset 0 0 0 1px var(--nfcg-line);
}

/* The winner's row fills and their name takes the indigo. The loser recedes
   rather than being marked: the badge already says it, and this is the board
   that gets shared. Neither applies to a draw. */
.nfcg-match--over .nfcg-seat:has(.nfcg-seat__result--win) {
	background: #f6f2ff;
	border-color: #c9b6f5;
}

.nfcg-match--over .nfcg-seat:has(.nfcg-seat__result--win) .nfcg-seat__name {
	color: var(--nfcg-indigo-dark);
}

.nfcg-match--over .nfcg-seat:has(.nfcg-seat__result--loss) {
	opacity: 0.62;
}

/* Top LEFT: .nfcg-seat__presence anchors to the right of this same box, and
   the two collided in the first cut. */
.nfcg-seat__crown {
	position: absolute;
	top: -0.6rem;
	left: 0.8rem;
	width: 1.5rem;
	height: 1.5rem;
	color: var(--nfcg-accent);
	fill: currentcolor;
}

/* ------------------------------------------------------------ the curtain

   Thrown over the board once, at the moment a match ends under an open page —
   never on a board opened after the fact, where it would cover the very thing
   the visitor came for. See showCurtain(); the dismiss is not optional. */

.nfcg-board__curtain {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 1.5rem;
	text-align: center;
	background: rgba(26, 17, 48, 0.72);
	backdrop-filter: blur(2px);
	border-radius: 12px;
	color: #fff;
	animation: nfcg-curtain 420ms ease-out both;
}

.nfcg-board__curtain-text {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
}

.nfcg-board__curtain-eyebrow {
	font-size: 0.6875rem;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: #d8c8ff;
}

.nfcg-board__curtain-name {
	font-family: var(--nfcg-display);
	font-size: clamp(1.3rem, 4vw, 1.9rem);
	font-weight: 700;
	line-height: 1.15;
}

.nfcg-board__curtain-how {
	color: #cfc6e6;
	font-size: 0.95rem;
}

.nfcg-board__curtain-dismiss {
	margin-top: 0.9rem;
	padding: 0.4rem 0.9rem;
	border: 1px solid rgba(255, 255, 255, 0.35);
	border-radius: 999px;
	background: transparent;
	color: #fff;
	font: inherit;
	font-size: 0.8125rem;
	cursor: pointer;
}

.nfcg-board__curtain-dismiss:hover,
.nfcg-board__curtain-dismiss:focus-visible {
	background: rgba(255, 255, 255, 0.14);
}

@keyframes nfcg-curtain {

	from {
		opacity: 0;
		transform: translateY(6px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/* The board underneath is what people came to look at, so a viewer who has
   asked for less motion gets the panel with no entrance rather than a slower
   one. */
@media (prefers-reduced-motion: reduce) {

	/* Belt and braces — animateMove() checks the same query and returns before
	   building a flyer at all. This catches one built just as the setting
	   changed. */
	.nfcg-flyer {
		display: none;
	}


	.nfcg-board__curtain {
		animation: none;
	}
}

/* Presence is suppressed on a terminal match (see presence_visible_to), so the
   seat row has nothing live left to say and the result carries it. */

/* ---------------------------------------------------------------- the aside */

.nfcg-moves__title {
	margin: 1.25rem 0 0.5rem;
	font-size: 0.9rem;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--nfcg-muted);
}

.nfcg-moves {
	max-height: 16rem;
	margin: 0;
	padding: 0.5rem 0.5rem 0.5rem 2.5rem;
	overflow-y: auto;
	border: 1px solid var(--nfcg-line);
	border-radius: 12px;
	background: var(--nfcg-surface-alt);
	font-family: ui-monospace, sfmono-regular, menlo, monospace;
	font-size: 0.85rem;
}

.nfcg-moves__item {
	padding: 0.1rem 0;
}

.nfcg-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin-top: 1rem;
}

.nfcg-btn {
	padding: 0.55rem 1.1rem;
	border: 0;
	border-radius: 10px;
	cursor: pointer;
	font-family: var(--nfcg-body);
	font-size: 0.9rem;
	font-weight: 600;
}

.nfcg-btn--primary {
	background: var(--nfcg-indigo);
	color: #fff;
}

.nfcg-btn--primary:hover {
	background: var(--nfcg-indigo-dark);
}

.nfcg-btn--danger {
	background: transparent;
	border: 1px solid #dc2626;
	color: #dc2626;
}

.nfcg-btn--danger:hover {
	background: #fee2e2;
}

.nfcg-btn--ghost {
	background: var(--nfcg-surface-alt);
	border: 1px solid var(--nfcg-line);
	color: var(--nfcg-ink);
	min-width: 2.5rem;
}

.nfcg-btn--ghost:hover {
	background: var(--nfcg-line);
}

.nfcg-replay {
	display: flex;
	align-items: center;
	gap: 0.35rem;

	/* Wraps rather than spilling out of the side column, which at 263px is
	   narrower than the bar's 323px — it was marginal with four controls and
	   became a real overhang when autoplay made it five. flex-wrap rather than
	   a media query, like the rest of this stylesheet: the column's width here
	   is set by its container, not by the viewport. */
	flex-wrap: wrap;
	justify-content: center;
}


/* Sized in rem rather than em: these sit in a ghost button whose font-size is
   inherited, and the two arrows either side are text at that size. */
.nfcg-btn__icon {
	width: 0.9rem;
	height: 0.9rem;
	fill: currentcolor;
	pointer-events: none;
}

.nfcg-replay__ply {
	min-width: 4rem;
	color: var(--nfcg-muted);
	font-size: 0.85rem;
	text-align: center;
	font-variant-numeric: tabular-nums;
}

.nfcg-moves__item.is-current {
	background: var(--nfcg-line);
	border-radius: 4px;
	font-weight: 700;
}

.nfcg-notice {
	margin: 0.75rem 0 0;
	color: var(--nfcg-muted);
	font-size: 0.9rem;
}

.nfcg-notice.is-error {
	color: #991b1b;
}

.nfcg-notice.is-warning {
	color: #92400e;
}

.nfcg-notice.is-info {
	color: var(--nfcg-indigo);
}

/* ------------------------------------------------------------- Word Duel */

/* A word board has no light and dark squares. The premium squares carry the
   colour instead — chequering underneath them would make both unreadable, and
   the premiums are the information that matters. */
.nfcg-board__grid.is-wordduel .nfcg-square {
	background: var(--nfcg-wd-plain);
	border: 1px solid rgba(26, 17, 48, 0.08);

	/* 15 squares across, so the glyphs have to shrink with the board or the
	   tiles overflow their cells on a phone. */
	font-size: clamp(0.5rem, 3.2cqw, 1.1rem);
	font-weight: 700;
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-plain {
	background: var(--nfcg-wd-plain);
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-dl {
	background: var(--nfcg-wd-dl);
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-tl {
	background: var(--nfcg-wd-tl);
	color: var(--nfcg-wd-tl-ink);
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-dw {
	background: var(--nfcg-wd-dw);
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-tw {
	background: var(--nfcg-wd-tw);
	color: var(--nfcg-wd-tw-ink);
}

.nfcg-board__grid.is-wordduel .nfcg-square.is-premium-start {
	background: var(--nfcg-wd-start);
	color: var(--nfcg-wd-start-ink);
}

/* The start square's mark. A child element rather than a pseudo-element:
   ::after is already the move-target dot and .nfcg-tile__value owns the
   bottom-right corner, so both are spoken for. */
.nfcg-board__grid.is-wordduel .nfcg-square__star {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 55%;
	height: 55%;
	transform: translate(-50%, -50%);
	fill: currentcolor;
	opacity: 0.85;

	/* The square is the button. A star that swallowed the click would make the
	   one square every opening has to cover the one square you cannot tap. */
	pointer-events: none;
}

/* A tile, whether it is on the board or still on the rack. Tile colour beats
   square colour, so a played word reads as a word rather than as a pattern. */
.nfcg-board__grid.is-wordduel .nfcg-square[data-piece] {
	background: #fdf6e3;
	color: var(--nfcg-ink);
	box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.12);
}

/* The client does not draw the star under a tile, and this is the belt to that
   brace: a star showing through a played letter would read as a premium the
   centre does not carry. */
.nfcg-board__grid.is-wordduel .nfcg-square[data-piece] .nfcg-square__star {
	display: none;
}

/* A tile staged this turn but not yet submitted. It has to be obviously
   provisional, or a player cannot tell what they are about to commit. */
.nfcg-board__grid.is-wordduel .nfcg-square.is-staged[data-piece] {
	background: #fff7cd;
	box-shadow: inset 0 0 0 2px var(--nfcg-accent);
}

.nfcg-tile__value {
	position: absolute;
	right: 8%;
	bottom: 4%;
	font-size: 0.55em;
	font-weight: 600;
	line-height: 1;
	opacity: 0.75;
}

/* --------------------------------------------------- the premium legend */

/* Nothing on the board said what its colours meant, so the premiums — the
   whole of the scoring, and the reason to play one square over another — were
   information a new player could only get by losing.

   Under the board rather than in the aside: it is a key to the grid, it wants
   the grid's full width, and the aside re-flows below the board for a
   spectator, which would put the key further from the thing it explains. */
.nfcg-legend {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem 0.85rem;
	margin: 0.75rem 0 0;
	padding: 0;
	list-style: none;
	font-size: 0.78rem;
	color: var(--nfcg-muted);
}

.nfcg-legend__item {
	display: flex;
	align-items: center;
	gap: 0.35rem;
}

/* The colour is the cue being explained, so the swatch carries a border for
   the same reason the presence dot has text beside it: a colour on its own is
   not a status, and two of these are pale enough to vanish on white. */
.nfcg-legend__swatch {
	position: relative;
	display: inline-block;
	width: 0.9rem;
	height: 0.9rem;
	border: 1px solid rgba(26, 17, 48, 0.15);
	border-radius: 2px;
	background: var(--nfcg-wd-plain);
}

.nfcg-legend__swatch--dl {
	background: var(--nfcg-wd-dl);
}

.nfcg-legend__swatch--tl {
	background: var(--nfcg-wd-tl);
	color: var(--nfcg-wd-tl-ink);
}

.nfcg-legend__swatch--dw {
	background: var(--nfcg-wd-dw);
}

.nfcg-legend__swatch--tw {
	background: var(--nfcg-wd-tw);
	color: var(--nfcg-wd-tw-ink);
}

.nfcg-legend__swatch--start {
	background: var(--nfcg-wd-start);
	color: var(--nfcg-wd-start-ink);
}

/* The same mark the board draws, from the same sprite, at swatch size. */
.nfcg-legend__swatch .nfcg-square__star {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 70%;
	height: 70%;
	transform: translate(-50%, -50%);
	fill: currentcolor;
}

/* ------------------------------------------------------------- the rack */

.nfcg-rack {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem;
	margin-bottom: 0.75rem;
}

.nfcg-tile {
	position: relative;
	width: 2.4rem;
	height: 2.4rem;
	padding: 0;
	border: 1px solid #d6c9a8;
	border-radius: 6px;
	background: #fdf6e3;
	color: var(--nfcg-ink);
	box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.12);
	cursor: pointer;
	font-family: var(--nfcg-display);
	font-size: 1.1rem;
	font-weight: 700;
}

.nfcg-tile.is-armed {
	border-color: var(--nfcg-indigo);
	box-shadow:
		inset 0 -3px 0 rgba(0, 0, 0, 0.12),
		0 0 0 3px rgba(109, 40, 217, 0.3);
	transform: translateY(-3px);
}

/* Placed on the board this turn: shown greyed rather than removed, because a
   rack that reshuffles itself under the player's hand is horrible to use. */
.nfcg-tile.is-spent {
	opacity: 0.3;
	cursor: default;
}

.nfcg-tile:disabled {
	cursor: default;
}

/* ----------------------------------------------------- the games list */

.nfcg-game-grid {
	display: flex;
	flex-wrap: wrap;
	gap: 1rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.nfcg-game-card {
	flex: 1 1 16rem;
	padding: 1.25rem;
	border: 1px solid var(--nfcg-line);
	border-radius: 16px;
	background: var(--nfcg-surface);
}

.nfcg-game-card.is-locked {
	background: var(--nfcg-surface-alt);
}

/* Looser than the 1.25 reset above: these titles are a bold display face and
   routinely wrap to three lines, which is where tight leading reads as
   squashed. */
.nfcg-game-card__name {
	margin: 0 0 0.35rem;
	font-size: 1.15rem;
	font-weight: 700;
	line-height: 1.35;
}

.nfcg-game-card__blurb {
	margin: 0 0 0.75rem;
	color: var(--nfcg-muted);
	font-size: 0.9rem;
}

.nfcg-game-card__points {
	margin: 0 0 0.75rem;
	font-size: 0.85rem;
	font-weight: 600;
	color: var(--nfcg-accent);
}

.nfcg-game-card__gate {
	margin: 0 0 0.75rem;
	font-size: 0.85rem;
	color: var(--nfcg-muted);
}

.nfcg-game-card__matches {
	margin: 0;
	padding: 0;
	list-style: none;
	border-top: 1px solid var(--nfcg-line);
	padding-top: 0.75rem;
}

.nfcg-game-card__matches li {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 0.5rem;
	padding: 0.25rem 0;
	font-size: 0.9rem;
}

.nfcg-game-card__when {
	color: var(--nfcg-muted);
	font-size: 0.8rem;
}
