/*
 * The shape of the screen.
 *
 * Small screens first. The board, the rack and the buttons all have to fit
 * without scrolling, because scrolling mid-word loses your place.
 */

.screen {
	min-height: 100svh;
	padding: var(--space-3);
	/*
	 * The home indicator on a modern iPhone sits over the bottom of the screen,
	 * and installed to the home screen the status bar sits over the top: the
	 * window is then the whole 926 points of glass rather than the 745 Safari
	 * leaves, and the clock is over the first 47 of them. In the browser the top
	 * inset is 0, because the chrome has that strip and reports it as its own,
	 * so this costs the tab nothing.
	 */
	padding-top: max(var(--space-3), env(safe-area-inset-top));
	padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
}

.screen--game {
	/* The teacher panel is positioned against this. */
	position: relative;
	display: grid;
	/*
	 * Every row sizes to its contents, and the whole stack is centred in whatever
	 * height is going. No row is singled out as the flexible one, because the
	 * panels here come and go: the door disappears once the game starts and the
	 * teacher is out of flow entirely, so a positional rule would hand the
	 * flexible row to the wrong child. The board's own cap below is what decides
	 * how big it gets.
	 */
	grid-auto-rows: auto;
	align-content: center;
	gap: var(--space-2);
	/*
	 * How wide the game is allowed to be. Named because the teacher's report is
	 * fixed to the window rather than laid out in this column, so it has nothing
	 * else to take its width from: two hand-kept figures would drift, and the
	 * report's would be the wrong one.
	 */
	--column: 40rem;
	max-width: var(--column);
	margin: 0 auto;
	/*
	 * Exactly one screen, and nothing hangs off it.
	 *
	 * svh, not vh: iOS Safari counts vh against the taller layout viewport, so a
	 * full-height column ends up with its last row under the browser toolbar.
	 *
	 * A fixed height rather than a floor, because the rows do not add up to a whole
	 * number of pixels — the rack takes its height from the tiles' aspect, so the
	 * furniture comes to 360.94px against a 360px budget on a 428x745 phone. A
	 * column that overruns the window by a fraction is still a scrollable page, and
	 * a phone will let the player drag the whole game about by a pixel, which reads
	 * as the layout coming loose. Centred, the fraction is given up half at each
	 * end and cannot be seen.
	 *
	 * clip, not hidden: hidden would leave it a scroll container, and the browser
	 * would scroll it to bring a focused square into view — the same trap the
	 * board's frame avoids. The cost is that a row which genuinely outgrows the
	 * window is now silently cut instead of becoming scrollable, so
	 * tools/screenshots/shoot.mjs reports the overrun as a number ("page scroll")
	 * and that is the only thing watching for it.
	 */
	height: 100svh;
	overflow: clip;
}

/*
 * A screen arriving over the one it is replacing.
 *
 * Both are up at once while they cross, and only one of them may be in the flow
 * of the page: two screens of 100svh stacked is a page twice the height of the
 * window, which is a game that can be scrolled — see the rule about that below.
 * So the arriving one is lifted out, and it is the arriving one rather than the
 * one leaving because the game screen is the one whose geometry must not be
 * disturbed: `inset: 0` with its own `max-width` and auto margins gives it the
 * same box it has in flow, capped and centred, and its explicit height wins over
 * the bottom offset.
 *
 * Fixed rather than absolute, so it is the window it is laid over rather than
 * whatever the body happens to be. That costs nothing here: `position` does not
 * make an element the containing block of a fixed descendant, so the poster
 * behind it is still hung on the window and still holds still — only a transform,
 * a filter or containment would trap it, which is why the crossing moves a
 * screen's contents and never a screen.
 *
 * Offstage is the same screen before it has anything on it. A game is asked for
 * before there is anything to show for it — the socket has to open and the first
 * state has to arrive — so it is built and laid out here, out of sight, while the
 * player goes on looking at the screen they pressed. `visibility` rather than
 * `display`, because a screen with no layout cannot be measured, and it takes the
 * presses with it: an invisible screen over the lobby that still swallowed them
 * would be a Cancel button that has stopped working.
 */
.screen--arriving {
	position: fixed;
	inset: 0;
}

.screen--offstage {
	visibility: hidden;
}

.screen--lobby {
	display: grid;
	place-items: center;
	/*
	 * A panel's plate runs off both edges, so that it is one sheet across the
	 * window at every phone width rather than a card with a few pixels of poster
	 * showing beside it, and a screen being replaced travels a whole panel's width
	 * sideways on its way out. Both are cut off at the window here rather than
	 * being allowed to drag the lobby into scrolling sideways.
	 *
	 * Clip on this axis only: `hidden` would make the column a scroll container in
	 * both directions, and this screen has to be free to grow downwards — a field's
	 * fault on a short phone is a line the lobby may need to scroll to.
	 */
	overflow-x: clip;
}

/*
 * Waiting for the host to start: the door is the whole screen. An empty board
 * behind it is nothing to look at and pushes everything else out of sight.
 */
.screen--waiting > .hint,
.screen--waiting > .rack,
.screen--waiting > .controls {
	display: none;
}

/*
 * The board and the teacher share a box, so the panel can be positioned against
 * the board and cannot reach the rack below it.
 */
.board-area {
	position: relative;
	display: grid;
	justify-items: center;
	min-height: 0;
	/*
	 * The board is the one thing here that goes to the edge of the window, so this
	 * row takes back the column's padding. It has to be the row rather than the
	 * board itself, because --board-max is a percentage of this box: widen the box
	 * and the board is allowed the whole window, still centred in it and still
	 * capped by the height left over. Whatever is inside and is not the board —
	 * the teacher — puts the padding back.
	 */
	margin-inline: calc(-1 * var(--space-3));
}

.screen--waiting > .board-area {
	display: none;
}

/*
 * The door may grow downwards, which the board's column may not.
 *
 * It is a form, not a game: nothing here is aimed at, so nothing is lost by
 * scrolling to it, and this is the same licence the lobby's own screen takes for
 * the same reason. What forces it is that the door has no fixed size — it is a
 * room code, a seat a player and a line a knock — and a 320x568 phone is 520px of
 * column against a panel that measures 480px with one seat filled and 555px with
 * four. Held to one window with `overflow: clip`, the surplus is cut at *both*
 * ends, because the column is centred: the header loses its top and the door
 * loses "Start the game", which is the one thing the host is here to press.
 *
 * The floor is `.screen`'s own, so a door that fits is still centred in the
 * window, and one that does not cannot be cut at the top: with the height a floor
 * rather than a fixed size there is no overflow left for `align-content` to
 * centre. Sideways stays clipped, since this screen arrives across a whole
 * window's width and the page must not be draggable that way.
 */
.screen--game.screen--waiting {
	height: auto;
	overflow-x: clip;
	overflow-y: visible;
}

/*
 * The header and the code panel are one card, because the poster is behind them.
 *
 * Everything on this screen is furniture drawn for the dark table, and the poster
 * shows through wherever two pieces of it do not meet. The gap between these two
 * is the only such seam left once the header has a fill of its own — and on a
 * 320px phone it falls exactly across the artwork's own TILEZ, which puts a bright
 * slot of somebody else's letters between the two cards. Widening it is the wrong
 * way round: at 16px the letters are legible enough to read as a picture gone
 * wrong. So the seam is closed instead, the two radii are squared where they meet
 * — see ui.css — and the artwork is left to frame the pair rather than to show
 * between them.
 *
 * The header is also let out to the full column here. The cap it wears otherwise
 * is the board's width, so that the two line up, and on this screen the board is
 * not drawn: kept, it makes the joined card a narrow block sitting on a wide one.
 */
.screen--game.screen--waiting {
	gap: 0;
}

.screen--game.screen--waiting > .scoreboard {
	/* Both halves of the cap, or the header shrinks to its own contents instead of
	   stretching: a grid item with auto margins is never stretched to its column. */
	width: auto;
	margin-inline: 0;
}

/*
 * The teacher's report is up, and nothing else on the screen answers until it is
 * closed.
 *
 * There is no scrim, and there must not be one. The report names squares and
 * offers to show where a better play would have gone, so the board it is talking
 * about has to stay exactly as legible as it was — what goes out of reach is
 * everything a player could otherwise act with.
 *
 * Which by then is almost nothing: the turn has already moved on, so the squares
 * are disabled, the rack will not lift and every button but Shuffle is off. This
 * is what closes that gap, and it says so as it does it. Not `inert`, because
 * the floor here includes Safaris that do not have it and a rule that silently
 * does nothing on the oldest phone is worse than one that does half the job
 * everywhere.
 */
.screen--lesson > .scoreboard,
.screen--lesson > .hint,
.screen--lesson > .rack,
.screen--lesson > .controls,
.screen--lesson .board {
	pointer-events: none;
}

/* Dimmed, but only the things that would otherwise be acted on. The scoreboard
   is left alone: it is where the score that has just been earned is, and reading
   the report against it is the point. */
.screen--lesson > .rack,
.screen--lesson > .controls {
	opacity: 0.45;
}

/*
 * What the board may take: whatever is left once the scoreboard, hint, rack and
 * buttons have had their share.
 *
 * --furniture is that share, and it is measured rather than guessed. What it has
 * to buy is the buttons: they are the bottom of the column and the last thing in
 * it, with enough spare that a device an inch shorter than the one it was
 * measured on does not lose them. Nothing may go below them: the column is
 * exactly one screen and clips, so a row underneath is not scrolled to, it is
 * cut. The chat is what would go there and it is parked for that reason — in the
 * sum it costs a 320px phone 12.7px squares.
 *
 * The budget is a ceiling for the tallest each row can be, not for the height it
 * happens to have. The hint is the row that grows on its own, and it may not:
 * every message it can carry has to fit one line at 320px, or the column outruns
 * the window and the board comes down over the words.
 *
 * Measured with tools/screenshots/shoot.mjs at each breakpoint: 22.5rem leaves
 * 20px under the buttons on a 390x664 phone and squares at 19.2px. It has to be
 * measured again whenever a row in this column changes height, or the buttons
 * drop off the bottom of a phone.
 */
.screen--game {
	--furniture: 22.5rem;
	--board-max: min(100%, calc(100svh - var(--furniture)));
}

.screen--game .board {
	justify-self: center;
}

.hint {
	min-height: 1.25rem;
	margin: 0;
	text-align: center;
	font-size: 0.875rem;
	color: var(--danger);
}

.controls {
	display: grid;
	gap: var(--space-2);
}

.controls__primary {
	display: grid;
}

/*
 * Five equal columns rather than a wrapping row: at 320px they come out at
 * 51.2px each — 288px of column less four gaps — which is past the 44px a thumb
 * needs, and nothing is ever left stranded on a line of its own.
 *
 * Equal, and that is what decides the labels rather than the other way about.
 * Content-sized the five do fit a 320px phone at this size, and Pass comes out
 * 31.6px wide, under the touch floor. So a label has to fit 51.2px and the type
 * steps down to the smallest the game sets anything in: measured, Shuffle is the
 * longest of the five at 39.7px, with Words at 34.9px, Recall 33.1px, Swap
 * 29.8px and Pass 25.7px. "Dictionary" is 56.5px and is why that button says
 * Words — see ui/controls.js.
 *
 * minmax(0, 1fr), not 1fr, and min-width: 0 on the buttons with it. A 1fr track
 * will not shrink below its content, so four labels plus their padding demanded
 * 330px inside a 320px phone and dragged the whole column — header, board and
 * all — sideways with them.
 */
.controls__secondary {
	display: grid;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: var(--space-2);
}

.controls__secondary .button {
	min-width: 0;
	padding-inline: var(--space-1);
	font-size: 0.6875rem;
}

/*
 * Wider screens.
 *
 * The board is the hero, so everything else is pulled in to its width instead of
 * being allowed to stretch: left alone, the header became a pair of bars twice
 * as wide as the board with the scores nowhere near the game. Only here, though
 * — on a phone the column is already the width of the screen, and capping it to
 * a board that is short-screen-limited leaves the header too narrow for two
 * cards side by side.
 */
@media (min-width: 48rem) {
	.screen--game {
		--column: 64rem;
		--furniture: 23.5rem;
	}

	.screen--game > .scoreboard,
	.screen--game > .controls,
	.screen--game > .chat {
		width: min(100%, var(--board-max));
		margin-inline: auto;
	}

	.controls {
		gap: var(--space-3);
	}

	.controls__secondary {
		gap: var(--space-3);
	}

	/* The row is the board's width here rather than a phone's, so the columns are
	   84px on a 1280x860 desktop and the type goes back to the size every other
	   button in the game is set in. */
	.controls__secondary .button {
		font-size: 0.875rem;
	}
}

/*
 * A short screen — a small phone, or a big one with the browser's chrome showing
 * — spends its height on the board, and the header makes do with less. What it
 * gives up is in ui.css, beside the rules it overrides.
 *
 * 21rem is the most the furniture may have here, and it is set half a rem below
 * that. 20.5rem leaves 14px under the buttons on a 320x568 phone and squares at
 * 14.9px, and the square has to stay above 14px: that is the size two settled
 * decisions were measured at — a cream tile at 5.7:1 on the green field, and a
 * drop target no smaller than a tap target. 21rem gives 14.4px and 21.5rem gives
 * 13.9px, at which both go stale.
 */
@media (max-height: 40rem) {
	.screen--game {
		--furniture: 20.5rem;
	}
}

/*
 * Portrait only.
 *
 * A fifteen-square board, a rack and a row of buttons do not fit in 390 pixels
 * of height: sized to fit, the squares come out at six pixels across. Rather
 * than ship a cramped landscape layout nobody wants, the game asks to be turned
 * upright. The manifest requests portrait when installed; this covers the rest.
 *
 * The height condition matters. Without it this would also hide the game from
 * every desktop browser, which is landscape by nature.
 */
.rotate {
	display: none;
}

@media (orientation: landscape) and (max-height: 30rem) {
	.rotate {
		position: fixed;
		inset: 0;
		z-index: var(--layer-takeover);
		display: grid;
		place-items: center;
		padding: var(--space-4);
		background: var(--surface);
		text-align: center;
	}

	.rotate__message {
		font-size: var(--step-2);
		color: var(--accent);
	}
}
