/*
 * The rack.
 *
 * Seven tiles at a size that stays comfortably tappable without ever growing to
 * dominate the screen. The trough is the light wood a tile rack is made of,
 * dark enough that cream tiles stand off it.
 *
 * The size is stated outright rather than derived from an aspect ratio inside
 * flexible columns. A grid item's automatic minimum width beats its max-width,
 * so a square inferred from a stretched row height can force the whole rack far
 * wider than any cap put on it. Saying what the tile measures leaves nothing to
 * infer.
 */

/*
 * The tile size, worked out rather than guessed at.
 *
 * Seven tiles, six gaps and two lots of padding — the trough's and the screen's —
 * which comes to 3.625rem of furniture all told. Dividing what is left by seven
 * means the rack fits any width by construction instead of relying on a minimum
 * that happens to suit the phone it was tried on.
 *
 * The gaps and the trough's lip are as narrow as they are because every pixel
 * spent on them is a pixel off the tiles, and the tiles are what a thumb has to
 * hit: 320px of screen cannot give seven of them the 44px a touch target wants,
 * so the arithmetic here is what decides how close it gets. Tiles in a real rack
 * touch each other anyway. Capped at 3.5rem, past which a tile is bigger than a
 * thumb needs.
 */
:root {
	--rack-gap: 0.1875rem;
	--rack-lip: var(--space-1);
	--rack-tile: clamp(2rem, (100vw - 3.625rem) / 7, 3.5rem);
	/* A full rack, which is what the trough is: the same seven the tile size is
	   divided out of, their gaps, and the lip either side. */
	--rack-width: calc(7 * var(--rack-tile) + 6 * var(--rack-gap) + 2 * var(--rack-lip));
}

.rack {
	display: flex;
	justify-content: center;
	gap: var(--rack-gap);
	/* Only as wide as the tiles need. Stretched across a desktop the rack reads as
	   a long empty trough with seven tiles adrift in the middle of it. */
	width: fit-content;
	max-width: 100%;
	/*
	 * But never narrower than a full rack, whatever is left in it. The trough is a
	 * piece of furniture and does not shrink because tiles have been taken out of
	 * it: a rack that closed up around four letters would change width on every
	 * placement, and the board above it is measured against a column whose parts
	 * hold still. The tiles centre in what is then a fixed run of wood, so they
	 * stay under the thumb rather than huddling at one end.
	 *
	 * At the 320px floor this is exactly 100%, so it can never fight the cap above
	 * it — min-width would win if it did.
	 */
	min-width: min(100%, var(--rack-width));
	margin-inline: auto;
	/* Deeper at the front, as a rack is, and that is height rather than width so it
	   costs the tiles nothing. */
	padding: var(--rack-lip) var(--rack-lip) calc(var(--rack-lip) * 2.5);
	/*
	 * And never shallower than a full rack, for the reason it is never narrower: the
	 * trough is furniture, and its height is the tiles' to give only while there are
	 * some in it. A rack with every tile out on the board is padding alone — 3.5px of
	 * wood on a 320px phone — so the board above it would grow as a word was built
	 * and shrink again as it was taken back.
	 *
	 * The same arithmetic the full trough comes to, so it is a floor that never binds
	 * on a rack that has anything in it: one tile, the lip above it and the deeper lip
	 * below. Border-box, so the padding is inside the figure.
	 */
	min-height: calc(var(--rack-tile) + var(--rack-lip) * 3.5);
	background: linear-gradient(180deg, var(--wood-light), var(--wood) 40%, var(--wood-dark));
	border-radius: var(--radius);
	box-shadow:
		inset 0 0.35rem 0.6rem rgb(0 0 0 / 0.45),
		inset 0 -1px 0 rgb(255 255 255 / 0.1),
		0 0.4rem 0.9rem -0.3rem rgb(0 0 0 / 0.5);
}

.rack-tile,
.code-tile,
.dictionary__tile {
	position: relative;
	flex: 0 0 auto;
	inline-size: var(--rack-tile);
	block-size: var(--rack-tile);
	/* The same rounding as a tile on the board, which is a share of its size. */
	border-radius: 8%;
}

/*
 * What a tile is made of, away from the board's container query: a rack tile, a
 * tile in the air, the tiles a room code is spelled out in, and the copy of the
 * rack the dictionary shows. One declaration for all of them, or the faces drift
 * apart — which they did, the dictionary's own copy of it printing a 16px letter
 * on a 55px tile where every other tile in the game prints one at 60% of itself.
 *
 * Everything here is a share of --rack-tile, so a user of it that wants another
 * size sets that one property and the letter and the value follow.
 */
:is(.rack-tile, .code-tile, .dictionary__tile) {
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	background: linear-gradient(var(--tile-tilt, 160deg), var(--tile-light) 0%, var(--tile) 48%, var(--tile-deep) 100%);
	box-shadow:
		inset 0 0 0 1px var(--tile-edge),
		inset 0 2px 0 rgb(255 255 255 / 0.6),
		inset 0 -2px 0 rgb(0 0 0 / 0.12),
		0 2px 4px rgb(0 0 0 / 0.45);
	color: var(--tile-ink);
}

/*
 * A rack tile is the one thing on this screen that is forever being moved:
 * dragged, lifted by a keyboard, hopped about by a shuffle, flown home by a
 * recall. None of this belongs on a tile that only has a letter printed on it.
 *
 * will-change must stay. Gaining a transform from nothing rebuilds the paint
 * property tree, and this page's board is a container query whose 100-odd
 * premium labels are then re-shaped to suit: 19ms, measured, for one tile. Said
 * up front, the transform node is already there, every later change costs
 * nothing, and a flight neither starts nor ends with a stall.
 */
.rack-tile {
	will-change: transform;
	/* A press on a tile belongs to the drag. Without this the browser claims the
	   gesture as a scroll and the tile never moves. */
	touch-action: none;
}

/* Gold on the trough is 1.8:1, so a focused tile rings itself in the same accent
   the board uses. */
.rack-tile:focus-visible {
	outline-color: var(--board-accent);
}

/* As on the board: neighbours never catch the light identically. */
:is(.rack-tile, .code-tile, .dictionary__tile):nth-child(3n + 1) { --tile-tilt: 152deg; }
:is(.rack-tile, .code-tile, .dictionary__tile):nth-child(3n + 2) { --tile-tilt: 168deg; }
:is(.rack-tile, .code-tile, .dictionary__tile):nth-child(3n) { --tile-tilt: 160deg; }

:is(.rack-tile, .code-tile, .dictionary__tile) .tile__letter {
	font-family: var(--font-tile);
	/* Scales with the tile so a small screen keeps the letter in proportion, and
	   sized as the board's letters are: a cap height of half the tile. What caps
	   it is the value in the corner — see the board's own note: Q and Z are worth
	   ten, and a letter grown past this meets those two digits. */
	font-size: calc(var(--rack-tile) * 0.6);
	font-weight: 700;
	line-height: 1;
}

/*
 * Everything about the value is a share of the tile, as it is on the board, so a
 * rack tile of any size prints the same corner: in from both edges rather than
 * against the bevel, and small enough to stay under the letter's baseline, which
 * is what keeps the widest value — the ten of a Q or a Z — clear of it.
 */
:is(.rack-tile, .code-tile, .dictionary__tile) .tile__value {
	position: absolute;
	right: calc(var(--rack-tile) * 0.12);
	bottom: calc(var(--rack-tile) * 0.03);
	font-family: var(--font-tile);
	font-size: calc(var(--rack-tile) * 0.21);
	font-weight: 700;
	line-height: 1;
	opacity: 0.85;
}

/* The tile a keyboard player has chosen lifts, so it is obvious which one the
   next square activated will take. */
.rack-tile--selected {
	box-shadow:
		inset 0 0 0 1px var(--tile-edge),
		inset 0 2px 0 rgb(255 255 255 / 0.7),
		0 0 0 3px var(--board-accent),
		0 8px 12px rgb(0 0 0 / 0.5);
}

/*
 * A tile being carried. It stays in the rack rather than being replaced by an
 * empty slot, because the element a touch started on has to outlive the gesture,
 * so it is faded to the trough instead.
 */
.rack-tile--lifted {
	opacity: 0.2;
	box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.5);
}

/*
 * The rack parting round a tile being carried along it.
 *
 * The offsets themselves are written by the view, from its own measurements: the
 * tiles cannot be redrawn into a new order, because the element a touch started
 * on has to outlive the gesture. All this adds is the travel, so the rack takes
 * up each arrangement rather than snapping between them, and the gap the carried
 * tile is heading for opens where the eye can follow it.
 *
 * Scoped to a rack that is being sorted, and it must stay that way: a shuffle
 * and a recall move these same tiles with keyframed animations of their own, and
 * a standing transition on transform would take over the moment one of those is
 * called off — which is every time the player touches the screen.
 */
@media (prefers-reduced-motion: no-preference) {
	.rack--sorting .rack-tile {
		transition: transform 140ms ease;
	}
}

/* The trough lights up when a tile dragged off the board would come home. */
.rack--drop {
	background: linear-gradient(180deg, var(--wood-light), var(--wood) 40%, var(--wood-dark));
	box-shadow:
		inset 0 0.35rem 0.6rem rgb(0 0 0 / 0.45),
		inset 0 0 0 2px var(--board-accent),
		0 0.4rem 0.9rem -0.3rem rgb(0 0 0 / 0.5);
}

/* --- a tile in flight --- */

/*
 * The layer a dragged tile is carried in.
 *
 * Fixed, above everything the game draws and below the prompt to turn the phone
 * upright, with pointer events off: the hit test under the pointer has to find
 * the square, not the tile on its way to it. The position is two custom
 * properties the drag sets, so following a finger only ever changes a transform.
 *
 * The tile sits on the pointer, centred: what is under the finger is what will
 * be played, and the tile is the only thing saying so.
 */
.drag-layer {
	position: fixed;
	left: 0;
	top: 0;
	z-index: var(--layer-drag);
	pointer-events: none;
	transform:
		translate(var(--drag-x, 0px), var(--drag-y, 0px))
		translate(-50%, -50%);
	will-change: transform;
}

/*
 * Over a square, the carried tile is that square: --drag-cell is the width the
 * board reports for the cell the pointer is in, and everything a tile is made of
 * is a share of --rack-tile, so overriding the one property takes the letter and
 * the value down with the tile rather than leaving rack-sized text in a 19px
 * square.
 */
.drag-layer--placing {
	--rack-tile: var(--drag-cell);
}

/*
 * A tile held on the pointer. It casts further than one at rest, which is what
 * says it is above the board rather than on it.
 *
 * The ghost alone, and a tile the game is flying may not have it. A cast that
 * deep is four times the blur of a tile at rest — measured, 16px against 4px —
 * so a flight that put it on and took it off again would be a pop at either end
 * of every hop, and the shuffle takes it off all seven at once when the last one
 * lands. A ghost never lands: it is thrown away and the real tile appears, so
 * there is nothing for the change to be seen against. What lifts a flying tile
 * instead is its own arc and the scale it takes at the top of it, both of which
 * carry the shadow with them because a transform scales what an element paints.
 *
 * Held rather than placed: larger than the square it is heading for, and tilted
 * off the grid. Over a square it is neither — it is lying in the square, so it
 * sits square and at the size the board gave it.
 */
.drag-ghost {
	box-shadow:
		inset 0 0 0 1px var(--tile-edge),
		inset 0 2px 0 rgb(255 255 255 / 0.6),
		0 0.6rem 1rem -0.2rem rgb(0 0 0 / 0.55);
	transform: scale(1.15) rotate(-4deg);
}

.drag-layer--placing .drag-ghost {
	transform: none;
}

/*
 * A tile in flight passes over the board, so it has to be lifted above it —
 * every square is a positioned element, and a tile flying home would otherwise
 * be painted among them rather than over them. Kept below the sheets, the toasts
 * and the tile a pointer is carrying, all of which sit over the whole game.
 */
.rack-tile--flying {
	z-index: var(--layer-flying);
}

/*
 * Where somebody else's tiles come down from their card in the header onto the
 * board.
 *
 * Fixed to the window and never transformed, because a tile is placed here with
 * the numbers the board reported for its square: the two have to be reading the
 * same coordinates. Nothing else would do — the board's frame clips, so a tile
 * that is genuinely coming from outside the board cannot be drawn inside it.
 *
 * Over the board and the header the tiles cross, and under everything else the
 * game puts on the table — the toasts included. A tile in the air is neither
 * being read nor being aimed at, so it gives way to whatever is. See the layer
 * list in tokens.css.
 */
.arrivals {
	position: fixed;
	inset: 0;
	z-index: var(--layer-arrival);
	pointer-events: none;
}

/*
 * An arriving tile is already lying in the square it is heading for: placed at
 * that cell's size, square to the grid. --rack-tile is what everything a tile is
 * made of is a share of, so setting it per tile brings the letter and the value
 * down with it rather than leaving hand-sized text in a 19px square.
 *
 * The resting transform has to be none, and has to be declared. None, because
 * the arc, the lean and the shrink are the flight's to write and it hands the
 * tile back to its own style when it lands — a ghost still wearing the drag's
 * tilt would jerk on arrival. Declared, because an element that gains a transform
 * from nothing has the paint property tree rebuilt around it mid-flight.
 */
.arrivals .drag-ghost {
	position: absolute;
	transform: none;
}
