/* The component library. Every class here has a matching templ component in
   internal/ui/components.templ; /styleguide renders all of them. */

/* --- buttons ------------------------------------------------------------- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--sp-2);
	min-height: var(--tap);
	padding: var(--sp-2) var(--sp-6);
	border: 1px solid transparent;
	border-radius: var(--r-md);
	background: var(--c-brand);
	color: var(--c-brand-ink);
	font: inherit;
	font-weight: 700;
	text-decoration: none;
	cursor: pointer;
	transition: filter var(--duration), background var(--duration);
}

.btn:hover:not(:disabled) {
	filter: brightness(1.08);
}

.btn:disabled,
.btn[aria-disabled="true"] {
	opacity: 0.5;
	cursor: not-allowed;
}

.btn-secondary {
	background: var(--c-surface);
	color: var(--c-text);
	border-color: var(--c-border);
}

.btn-ghost {
	background: transparent;
	color: var(--c-muted);
	padding-inline: var(--sp-3);
}

.btn-ghost:hover:not(:disabled) {
	background: var(--c-surface-2);
	color: var(--c-text);
}

.btn-danger {
	background: var(--c-bad);
	color: #fff;
}

.btn-lg {
	min-height: 56px;
	padding-inline: var(--sp-8);
	font-size: var(--fs-lg);
}

.btn-block {
	width: 100%;
}

/* --- form fields --------------------------------------------------------- */

.field {
	display: flex;
	flex-direction: column;
	gap: var(--sp-1);
}

.field > label {
	font-weight: 600;
}

.field .hint {
	color: var(--c-muted);
	font-size: var(--fs-sm);
}

.field .error {
	color: var(--c-bad);
	font-size: var(--fs-sm);
	font-weight: 600;
}

.input,
.textarea,
.select {
	min-height: var(--tap);
	padding: var(--sp-2) var(--sp-3);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	background: var(--c-surface);
	color: var(--c-text);
	font: inherit;
	width: 100%;
}

.textarea {
	min-height: calc(var(--tap) * 2);
	resize: vertical;
}

.input[aria-invalid="true"],
.textarea[aria-invalid="true"] {
	border-color: var(--c-bad);
}

/* --- surfaces ------------------------------------------------------------ */

.card {
	padding: var(--sp-6);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	box-shadow: var(--shadow-1);
}

.card-title {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--sp-3);
}

.badge {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-1);
	padding: 2px var(--sp-2);
	border-radius: var(--r-pill);
	background: var(--c-surface-2);
	color: var(--c-muted);
	font-size: var(--fs-sm);
	font-weight: 600;
}

.badge-ok {
	background: color-mix(in srgb, var(--c-ok) 16%, transparent);
	color: var(--c-ok);
}

.badge-bad {
	background: color-mix(in srgb, var(--c-bad) 16%, transparent);
	color: var(--c-bad);
}

.empty-state {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-12) var(--sp-6);
	border: 1px dashed var(--c-border);
	border-radius: var(--r-lg);
	text-align: center;
	color: var(--c-muted);
}

/* --- dialog -------------------------------------------------------------- */

.dialog {
	/* Narrower than its container as well as the viewport: a modal is positioned against the
	   viewport, but the style guide renders one in normal flow inside a card. */
	width: min(32rem, calc(100% - var(--sp-4)), calc(100vw - var(--sp-8)));
	padding: var(--sp-6);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	color: var(--c-text);
	box-shadow: var(--shadow-2);
}

.dialog::backdrop {
	background: rgb(12 14 24 / 0.55);
}

/* --- toasts -------------------------------------------------------------- */

.toast-stack {
	position: fixed;
	inset-block-end: var(--sp-4);
	inset-inline: var(--sp-4);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-2);
	pointer-events: none;
	z-index: 50;
}

.toast {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	max-width: 32rem;
	padding: var(--sp-3) var(--sp-4);
	border-radius: var(--r-md);
	background: var(--c-text);
	color: var(--c-bg);
	box-shadow: var(--shadow-2);
	pointer-events: auto;
}

.toast-ok {
	background: var(--c-ok);
	color: #fff;
}

.toast-bad {
	background: var(--c-bad);
	color: #fff;
}

/* --- answer tiles -------------------------------------------------------- */

/* Four tiles fill a phone screen in a 2x2 grid with nothing to scroll. */
.tile-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--sp-2);
	flex: 1;
	min-height: 0;
}

.tile-grid.tiles-3 > :last-child,
.tile-grid.tiles-1 > :only-child {
	grid-column: 1 / -1;
}

.tile {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	min-height: var(--tap);
	padding: var(--sp-4);
	border: 4px solid transparent;
	border-radius: var(--r-md);
	background: var(--tile-color);
	color: #fff;
	font: inherit;
	font-size: var(--fs-lg);
	font-weight: 700;
	text-align: start;
	cursor: pointer;
	transition: transform var(--duration), filter var(--duration);
}

.tile:hover:not(:disabled) {
	filter: brightness(1.08);
}

.tile:active:not(:disabled) {
	transform: scale(0.98);
}

/* Selection is carried by the outline and the rank badge, never by colour alone. */
.tile[aria-pressed="true"] {
	border-color: var(--c-text);
	box-shadow: var(--shadow-2);
}

.tile:disabled {
	opacity: 0.45;
	cursor: not-allowed;
}

.tile-dimmed {
	opacity: 0.35;
}

.tile-shape {
	flex: none;
	width: 2em;
	height: 2em;
	fill: currentcolor;
}

.tile-label {
	flex: 1;
	min-width: 0;
	overflow-wrap: anywhere;
}

.tile-rank {
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 1.8em;
	height: 1.8em;
	border-radius: var(--r-pill);
	background: rgb(255 255 255 / 0.28);
	font-variant-numeric: tabular-nums;
}

/* 5 to 8 options are rare; they repeat the four pairings with a thicker outline. */
.tile-repeat {
	border-style: dashed;
	border-color: rgb(255 255 255 / 0.75);
}

/* --- countdown ----------------------------------------------------------- */

.countdown {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-2);
	font-variant-numeric: tabular-nums;
	font-weight: 800;
	font-size: var(--fs-lg);
}

.countdown-dial {
	--progress: 1;
	position: relative;
	flex: none;
	width: 2.2em;
	height: 2.2em;
	border-radius: 50%;
	/* One conic sweep, driven by a custom property the server (or the client clock) updates. */
	background: conic-gradient(var(--c-brand) calc(var(--progress) * 360deg), var(--c-surface-2) 0);
}

.countdown-dial::after {
	content: "";
	position: absolute;
	inset: 18%;
	border-radius: 50%;
	background: var(--c-bg);
}

.countdown-urgent {
	color: var(--c-bad);
}

.countdown-urgent .countdown-dial {
	--c-brand: var(--c-bad);
	animation: pulse 1s ease-in-out infinite;
}

@keyframes pulse {
	50% {
		transform: scale(1.08);
	}
}

/* --- media --------------------------------------------------------------- */

.media-figure {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-2);
	margin: 0;
	min-height: 0;
}

.media-figure img,
.media-figure video {
	max-height: 45dvh;
	border-radius: var(--r-md);
	object-fit: contain;
	background: var(--c-surface-2);
}

.media-figure figcaption {
	color: var(--c-muted);
	font-size: var(--fs-sm);
}

/* --- language switch ----------------------------------------------------- */

.lang-switch {
	display: inline-flex;
	gap: var(--sp-1);
	padding: 2px;
	border: 1px solid var(--c-border);
	border-radius: var(--r-pill);
}

.lang-switch a {
	padding: 2px var(--sp-3);
	border-radius: var(--r-pill);
	color: var(--c-muted);
	font-size: var(--fs-sm);
	font-weight: 600;
	text-decoration: none;
}

.lang-switch a[aria-current="true"] {
	background: var(--c-brand);
	color: var(--c-brand-ink);
}

/* --- account corner ------------------------------------------------------ */

.avatar {
	width: 32px;
	height: 32px;
	border-radius: 50%;
	object-fit: cover;
	background: var(--c-surface-2);
}

/* --- media library ------------------------------------------------------- */

.dropzone {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--sp-3);
	padding: var(--sp-6);
	border: 2px dashed var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	text-align: center;
}

.dropzone-label {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-1);
	cursor: pointer;
}

.dropzone-cta {
	font-weight: 700;
	color: var(--c-brand);
	text-decoration: underline;
	text-underline-offset: 3px;
}

.dropzone-progress {
	font-weight: 600;
	color: var(--c-muted);
}

.media-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
	gap: var(--sp-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.media-card {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	padding: var(--sp-3);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

.media-thumb {
	width: 100%;
	aspect-ratio: 4 / 3;
	border-radius: var(--r-sm);
	object-fit: cover;
	background: var(--c-surface-2);
}

.media-meta {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-1);
	min-width: 0;
}

.media-name {
	overflow: hidden;
	font-weight: 600;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* The picker's cards are one big button. */
.media-pick {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: start;
	cursor: pointer;
}

.media-pick:hover .media-thumb {
	outline: 3px solid var(--c-brand);
	outline-offset: 2px;
}

/* Video and audio have no thumbnail; the icon stands in for one. */
.media-thumb-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--c-muted);
}

.media-thumb-icon svg {
	width: 40%;
	height: 40%;
	fill: currentcolor;
	stroke: currentcolor;
	stroke-width: 1.5;
	stroke-linejoin: round;
}

.media-thumb-icon path {
	fill: none;
}

/* --- quiz library -------------------------------------------------------- */

.quiz-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: var(--sp-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.quiz-card {
	display: flex;
	flex-direction: column;
	gap: var(--sp-3);
	padding: var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	box-shadow: var(--shadow-1);
}

.quiz-cover {
	display: block;
	aspect-ratio: 16 / 9;
	overflow: hidden;
	border-radius: var(--r-md);
	background: var(--c-brand-soft);
}

.quiz-cover img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.quiz-cover-blank {
	display: flex;
	align-items: center;
	justify-content: center;
	height: 100%;
	color: var(--c-brand);
	font-size: var(--fs-xl);
	font-weight: 800;
}

.quiz-title {
	color: var(--c-text);
	font-size: var(--fs-lg);
	font-weight: 700;
	text-decoration: none;
}

.quiz-title:hover {
	text-decoration: underline;
}

/* --- editor shell -------------------------------------------------------- */

.editor-columns {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: var(--sp-6);
	align-items: start;
}

.editor-summary {
	font-size: var(--fs-lg);
	font-weight: 700;
	cursor: pointer;
}

.editor-summary::marker {
	color: var(--c-muted);
}

.share-list {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* --- item outline editor -------------------------------------------------- */

.editor-panes {
	display: grid;
	grid-template-columns: minmax(280px, 26rem) 1fr;
	gap: var(--sp-4);
	align-items: start;
}

@media (max-width: 1100px) {
	.editor-panes {
		grid-template-columns: 1fr;
	}
}

.outline {
	padding: var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
}

.outline-list {
	display: flex;
	flex-direction: column;
	gap: var(--sp-1);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: outline;
}

.outline-row {
	display: flex;
	align-items: center;
	gap: var(--sp-1);
	border: 1px solid transparent;
	border-radius: var(--r-md);
}

.outline-row:hover {
	background: var(--c-surface-2);
}

.outline-row-selected {
	border-color: var(--c-brand);
	background: var(--c-brand-soft);
}

.outline-select {
	display: flex;
	flex: 1;
	align-items: center;
	gap: var(--sp-2);
	min-width: 0;
	min-height: var(--tap);
	padding: var(--sp-2);
	border: 0;
	border-radius: var(--r-md);
	background: none;
	color: inherit;
	font: inherit;
	text-align: start;
	cursor: pointer;
}

.outline-position {
	flex: none;
	width: 1.6em;
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
}

.outline-thumb {
	flex: none;
	width: 40px;
	height: 30px;
	border-radius: var(--r-sm);
	object-fit: cover;
	background: var(--c-surface-2);
}

.outline-text {
	display: flex;
	flex: 1;
	flex-direction: column;
	min-width: 0;
}

.outline-preview,
.outline-text .small {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.outline-preview {
	font-weight: 600;
}

/* The badge keeps its width; the preview beside it is what gives way. */
.outline-summary > .badge {
	flex: none;
	white-space: nowrap;
}

.outline-actions {
	display: flex;
	flex: none;
	gap: 0;
}

.outline-actions .btn {
	min-height: 32px;
	padding-inline: var(--sp-2);
}

/* The row being dragged, and the row it is over. */
.outline-row[draggable="true"] {
	cursor: grab;
}

.outline-summary {
	display: flex;
	flex: 1;
	align-items: center;
	gap: var(--sp-2);
	min-width: 0;
}

/* --- answer rows ---------------------------------------------------------- */

.option-list {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.option-row {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-2);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

.option-badge {
	display: flex;
	flex: none;
	align-items: center;
	justify-content: center;
	gap: var(--sp-1);
	min-width: 2.4em;
	height: 2.4em;
	padding-inline: var(--sp-1);
	border-radius: var(--r-sm);
	background: var(--tile-color);
	color: #fff;
}

.option-badge .tile-shape {
	width: 1.2em;
	height: 1.2em;
}

.option-rank {
	font-variant-numeric: tabular-nums;
	font-weight: 800;
}

.option-correct {
	display: flex;
	flex: none;
	align-items: center;
	justify-content: center;
	min-width: var(--tap);
	min-height: var(--tap);
}

.option-correct input {
	width: 1.15em;
	height: 1.15em;
}

.option-thumb {
	flex: none;
	width: 48px;
	height: 36px;
	border-radius: var(--r-sm);
	object-fit: cover;
	background: var(--c-surface-2);
}

.option-actions {
	display: flex;
	flex: none;
}

.option-actions .btn {
	min-height: 32px;
	padding-inline: var(--sp-2);
}

/* --- slides --------------------------------------------------------------- */

/* The stage is what the projector shows and what the editor previews, at whatever size its
   container gives it. Everything inside sizes in `em` so one scale factor moves the lot. */
.slide-stage {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	aspect-ratio: 16 / 9;
	overflow: hidden;
	border-radius: var(--r-md);
	background: var(--c-bg);
	color: var(--c-text);
}

.slide-media {
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 0;
	min-height: 0;
}

.slide-media img,
.slide-media video {
	width: 100%;
	height: 100%;
	object-fit: contain;
}

.slide-text {
	display: flex;
	flex-direction: column;
	gap: 0.5em;
	padding: 1.5em;
	min-width: 0;
}

.slide-title {
	font-size: 2em;
	font-weight: 800;
	line-height: 1.1;
	text-wrap: balance;
}

.slide-body {
	font-size: 1.1em;
	line-height: 1.4;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

/* media-full: the picture takes the whole stage it is given and is shown entire — letterboxed on
   the stage's ground rather than cropped to its 16/9. A portrait poster loses its top and bottom to
   a crop, which is exactly what nobody in the room can ask to see again.
   The title, when there is one, takes its band at the foot of the stage instead of sitting over the
   picture: the media stops where the text starts, so no part of it is behind the words. Its scrim
   stays, for the picture that ends right against it. */
.slide-media-full {
	flex-direction: column;
}

.slide-media-full .slide-media {
	flex: 1;
	align-self: stretch;
	min-height: 0;
}

.slide-media-full .slide-media img,
.slide-media-full .slide-media video {
	object-fit: contain;
}

.slide-media-full .slide-text {
	flex: none;
	width: 100%;
	color: #fff;
	background: linear-gradient(to top, rgb(0 0 0 / 0.85), rgb(0 0 0 / 0.55) 60%, transparent);
	text-shadow: 0 2px 8px rgb(0 0 0 / 0.6);
}

/* media-left: two halves. */
.slide-media-left {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: stretch;
}

.slide-media-left .slide-media {
	overflow: hidden;
}

/* The half a picture is given is a shape it never chose either, so it is fitted into it rather
   than cropped to it — the same bargain as media-full. */
.slide-media-left .slide-media img,
.slide-media-left .slide-media video {
	object-fit: contain;
}

.slide-media-left .slide-text {
	justify-content: center;
}

/* text-only: nothing but words, centred and large. */
.slide-text-only .slide-text {
	align-items: center;
	max-width: 34em;
	text-align: center;
}

/* The editor's preview is the same stage, shrunk. */
.slide-preview {
	container-type: inline-size;
	max-width: 34rem;
	font-size: 0.9rem;
}

.slide-preview audio {
	width: 90%;
}

/* --- Host control panel -------------------------------------------------- */

.host {
	--gap: var(--sp-4);
}

/* The banner only exists while the stream is down, so it takes the space it needs and no more. */
.host-offline {
	padding: var(--sp-2) var(--sp-4);
	border-radius: var(--r-pill);
	background: var(--c-bad);
	color: #fff;
	font-weight: 600;
	text-align: center;
}

.host-header {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-4);
	align-items: flex-start;
	padding: var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
	box-shadow: var(--shadow-1);
}

/* The PIN is read out loud and typed across a room: it gets the biggest type on the page, spaced
   so 6 and 8 cannot be confused, and tabular so it does not jitter as it is patched. */
.host-pin {
	font-size: var(--fs-display);
	font-weight: 800;
	line-height: 1;
	letter-spacing: 0.12em;
	font-variant-numeric: tabular-nums;
}

.host-panel {
	padding: var(--sp-6);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
	box-shadow: var(--shadow-1);
}

/* The rehearsal panel: the player's own screen, embedded in the host's page. It is deliberately
   phone-shaped and capped in height — it is a rehearsal of what a phone will show, and a full-width
   copy of it would read as a second control surface rather than as a preview. */
.host-rehearsal {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	padding: var(--sp-5);
	border: 1px dashed var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

.host-rehearsal-title {
	margin: 0;
	font-size: var(--fs-md);
}

.host-rehearsal-screen {
	width: min(24rem, 100%);
	min-height: 18rem;
	max-height: 30rem;
	margin-inline: auto;
	padding: var(--sp-4);
	overflow-y: auto;
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-bg);
}

/* The goodbye a live screen is given when the server is going away. It is appended to <body> by
   the stream itself, so it owns its own placement rather than a slot on the page. */
.server-notice {
	position: fixed;
	inset-inline: 0;
	bottom: 0;
	z-index: 100;
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	justify-content: center;
	padding: var(--sp-4);
	background: var(--c-text);
	color: var(--c-bg);
	text-align: center;
}

.host-progress {
	margin: 0;
}

.host-lobby {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-6);
	align-items: flex-start;
}

.host-qr {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	align-items: center;
	margin: 0;
	padding: var(--sp-3);
	border-radius: var(--r-md);
	background: #fff;
}

.host-qr img {
	display: block;
	width: 220px;
	height: 220px;
}

.host-players {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.host-chip {
	display: inline-flex;
	gap: var(--sp-2);
	align-items: center;
	padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3);
	border-radius: var(--r-pill);
	background: var(--c-brand-soft);
	color: var(--c-text);
	font-weight: 600;
	/* Names arrive one at a time on a live stream; the fade is how the room notices. */
	animation: host-chip-in var(--duration) ease-out;
}

@keyframes host-chip-in {
	from { opacity: 0; transform: scale(0.9); }
	to { opacity: 1; transform: none; }
}

/* A phone that lost its stream keeps its seat and its score, and says so quietly. */
.host-chip-away {
	background: var(--c-surface-2);
	color: var(--c-muted);
}

.host-chip-name {
	max-width: 12rem;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.host-chip-kick {
	display: grid;
	place-items: center;
	width: 1.75rem;
	height: 1.75rem;
	border: 0;
	border-radius: var(--r-pill);
	background: transparent;
	color: var(--c-muted);
	font-size: var(--fs-lg);
	line-height: 1;
	cursor: pointer;
}

.host-chip-kick:hover {
	background: var(--c-bad);
	color: #fff;
}

.host-stage {
	max-width: 40rem;
}

.host-prompt {
	margin: 0;
	font-size: var(--fs-lg);
}

.host-answered {
	margin: 0;
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
}

.host-tiles {
	display: grid;
	gap: var(--sp-2);
	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	margin: 0;
	padding: 0;
	list-style: none;
}

/* The host's tiles are read, never tapped: same colour and shape pairing as the room sees, at a
   size that fits four of them on a laptop beside everything else. */
.host-tile {
	position: relative;
	display: flex;
	gap: var(--sp-3);
	align-items: center;
	overflow: hidden;
	padding: var(--sp-3);
	border: 2px solid transparent;
	border-radius: var(--r-md);
	background: var(--c-surface-2);
	color: var(--c-text);
}

/* Marked from the moment the question opens: this is the one screen allowed to know. */
.host-tile-correct {
	border-color: var(--c-ok);
	background: color-mix(in srgb, var(--c-ok) 12%, var(--c-surface));
}

.host-tile .tile-shape {
	flex: none;
	width: 1.5rem;
	height: 1.5rem;
	fill: var(--tile-color);
}

.host-tile-label {
	flex: 1;
	min-width: 0;
	word-break: break-word;
}

.host-tile-rank,
.host-tile-count {
	flex: none;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.host-tile-mark {
	flex: none;
	color: var(--c-ok);
	font-weight: 700;
}

/* The distribution bar sits behind the row rather than beside it, so adding it at the reveal does
   not reflow the tiles the host has been looking at. */
.host-tile-bar {
	position: absolute;
	inset: auto 0 0 0;
	width: var(--bar, 0%);
	height: 4px;
	background: var(--c-brand);
	transition: width var(--duration) ease-out;
}

.host-scores {
	display: flex;
	flex-direction: column;
	gap: var(--sp-1);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: rank;
}

.host-score {
	display: flex;
	gap: var(--sp-3);
	align-items: center;
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-sm);
	background: var(--c-surface-2);
}

.host-score-rank,
.host-score-points {
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.host-score-rank {
	min-width: 2ch;
	color: var(--c-muted);
}

.host-score-name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.host-score-delta {
	color: var(--c-bad);
	font-size: var(--fs-sm);
	font-weight: 600;
}

.host-score-up {
	color: var(--c-ok);
}

.host-controls {
	gap: var(--sp-2);
	align-items: center;
	padding-top: var(--sp-2);
	border-top: 1px solid var(--c-border);
}

@media (prefers-reduced-motion: reduce) {
	.host-chip {
		animation: none;
	}

	.host-tile-bar {
		transition: none;
	}
}

/* --- Presenter screen ------------------------------------------------------
 *
 * The projector. Everything here is sized in `em` off the vmin type scale in tokens.css, so the
 * whole screen grows and shrinks as one; nothing scrolls, and nothing may overflow the viewport.
 */

.present-root,
.present-stage {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-4);
	min-height: 0;
}

.present-hud-title,
.present-hud-position,
.present-hud-players {
	margin: 0;
	font-size: var(--fs-sm);
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
}

.present-hud-title {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* The one control on a read-only screen. It is also the click that lets a browser start an
   unmuted video, which is why it is worth having even on a screen driven from elsewhere. */
.present-fullscreen {
	flex: none;
	padding: var(--sp-1) var(--sp-3);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	background: transparent;
	color: var(--c-muted);
	font-size: var(--fs-sm);
	line-height: 1;
	cursor: pointer;
}

.present-fullscreen:hover {
	color: var(--c-text);
	border-color: var(--c-text);
}

/* --- the host's control bar ---
 *
 * Only the signed-in host is served this markup; a projector opened with `?k=` never sees it.
 *
 * It is taken out of the flow on purpose. The room is looking at this screen, and a bar in the
 * column would push the question, the clock and the tiles up by its own height on every phase — so
 * it floats in the shell's bottom padding instead, and fades to almost nothing a few seconds after
 * the pointer leaves it. `:hover` and `:focus-within` bring it straight back, which also means it
 * is reachable by keyboard alone.
 *
 * The generous top padding is deliberate: it is the target a presenter remote's pointer, or a hand
 * on a room PC's trackball, has to land on to wake the bar up.
 */
/* The bar is out of the flow, so the question, the clock and the tiles sit exactly where the room's
   own projector puts them. What the host's screen does give up is the lane the bar lives in: a
   target big enough to hit with a trackball is taller than the shell's bottom padding, and a screen
   four vmin shorter is a better trade than a bar sitting on the answer tiles at 1920×1080. A
   read-only projector has neither the bar nor the lane. */
.present-stage-driven {
	padding-bottom: var(--sp-12);
}

.present-controls {
	position: fixed;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: 10;
	display: flex;
	gap: var(--sp-3);
	align-items: center;
	justify-content: center;
	padding: var(--sp-12) var(--sp-4) var(--sp-2);
	opacity: 0.14;
	transition: opacity 250ms ease 3s;
	pointer-events: none;
}

.present-controls > * {
	pointer-events: auto;
}

.present-controls:hover,
.present-controls:focus-within {
	opacity: 1;
	transition-delay: 0s;
}

/* Large hit targets: this is clicked on a room PC, often with a trackpad nobody enjoys using. */
.present-control {
	padding: var(--sp-3) var(--sp-6);
	border: 1px solid var(--c-border);
	border-radius: var(--r-pill);
	background: var(--c-surface);
	color: var(--c-text);
	font: inherit;
	font-size: var(--fs-sm);
	font-weight: 600;
	line-height: 1.2;
	cursor: pointer;
}

.present-control:hover:not(:disabled) {
	border-color: var(--c-text);
}

.present-control:disabled {
	opacity: 0.45;
	cursor: not-allowed;
}

.present-control-primary {
	border-color: var(--c-brand);
	background: var(--c-brand);
	color: var(--c-brand-ink);
}

@media (prefers-reduced-motion: reduce) {
	.present-controls {
		transition: none;
	}
}

/* --- lobby --- */

.present-lobby {
	display: grid;
	flex: 1;
	grid-template-columns: 1.4fr 1fr;
	grid-template-areas: "join qr" "roster roster";
	gap: var(--sp-6);
	align-items: center;
	min-height: 0;
}

.present-join {
	grid-area: join;
	min-width: 0;
}

.present-title {
	margin: 0;
	font-size: var(--fs-xl);
	font-weight: 800;
	line-height: 1.05;
	text-wrap: balance;
}

.present-join-hint {
	margin: var(--sp-2) 0 0;
	color: var(--c-muted);
	font-size: var(--fs-md);
	overflow-wrap: anywhere;
}

/* The PIN is read out loud and typed across a room: the biggest type on the screen, spaced so 6
   and 8 cannot be confused, tabular so it does not jitter as it is patched. */
.present-pin {
	margin: var(--sp-2) 0 0;
	font-size: var(--fs-display);
	font-weight: 800;
	line-height: 1;
	letter-spacing: 0.1em;
	font-variant-numeric: tabular-nums;
}

.present-qr {
	grid-area: qr;
	justify-self: end;
	margin: 0;
	padding: var(--sp-3);
	border-radius: var(--r-md);
	background: #fff;
}

.present-qr img {
	display: block;
	width: 34vmin;
	height: 34vmin;
}

.present-roster {
	grid-area: roster;
	align-self: start;
	min-height: 0;
	overflow: hidden;
}

.present-roster-count {
	margin: 0 0 var(--sp-2);
	color: var(--c-muted);
	font-size: var(--fs-md);
}

/* Names arrive on a live stream and have to stay readable at two hundred of them, so they lay out
   on a grid that fills the width rather than a line that wraps unevenly. */
.present-chips {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(16vmin, 100%), 1fr));
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.present-chip {
	overflow: hidden;
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-pill);
	background: var(--c-surface);
	font-size: var(--fs-sm);
	font-weight: 600;
	text-align: center;
	text-overflow: ellipsis;
	white-space: nowrap;
	animation: present-chip-in var(--duration) ease-out;
}

@keyframes present-chip-in {
	from { opacity: 0; transform: scale(0.85); }
	to { opacity: 1; transform: none; }
}

.present-chip-away {
	color: var(--c-muted);
	background: var(--c-surface-2);
}

.present-more,
.present-waiting {
	margin: var(--sp-3) 0 0;
	color: var(--c-muted);
	font-size: var(--fs-md);
}

/* --- slide --- */

.present-slide {
	display: flex;
	flex: 1;
	align-items: center;
	justify-content: center;
	min-height: 0;
}

/* On the projector the stage is the screen, not a card in a column: it takes the height it is
   given and keeps its ratio, rather than driving the height from the width. */
.present-slide .slide-stage {
	width: auto;
	height: 100%;
	max-width: 100%;
	aspect-ratio: 16 / 9;
	border-radius: var(--r-lg);
	font-size: 2.6vmin;
}

/* --- question --- */

.present-question {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-4);
	min-height: 0;
}

.present-prompt {
	margin: 0;
	font-size: var(--prompt-fs, 6vmin);
	font-weight: 800;
	line-height: 1.1;
	text-align: center;
	text-wrap: balance;
}

.present-middle {
	display: flex;
	flex: 1;
	gap: var(--sp-6);
	align-items: center;
	justify-content: space-between;
	min-height: 0;
}

/* The question's picture or clip takes the whole band between the prompt and the tiles, and is
   always shown entire: a portrait photo or a tall screenshot is scaled down until it fits rather
   than being run off the top and bottom of the wall, where .stage-shell's overflow would cut it.
   The media is laid out against this box rather than sized in percentages of it, so what it fits
   inside is the space actually measured on the night and never an indefinite height a percentage
   cannot resolve against. */
.present-figure {
	position: relative;
	flex: 1;
	align-self: stretch;
	margin: 0;
	min-width: 0;
	min-height: 0;
}

.present-figure img,
.present-figure video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border-radius: var(--r-md);
	object-fit: contain;
}

/* Audio has nothing to show, so it keeps the middle of the band rather than being stretched down
   it. */
.present-figure audio {
	position: absolute;
	top: 50%;
	left: 0;
	width: 100%;
	transform: translateY(-50%);
}

.present-answered,
.present-right-count {
	margin: 0;
	color: var(--c-muted);
	font-size: var(--fs-md);
	font-variant-numeric: tabular-nums;
}

.present-right-count {
	display: block;
	color: var(--c-ok);
}

/* The countdown: one conic gradient driven by --progress, which countdown.js writes every frame. No
   layout happens on a repaint, so a smooth dial costs nothing. */
.present-clock {
	position: relative;
	display: grid;
	flex: none;
	place-items: center;
	width: 18vmin;
	height: 18vmin;
	margin: 0;
}

.present-dial {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background:
		conic-gradient(var(--c-brand) calc(var(--progress, 0) * 360deg), var(--c-surface-2) 0);
	mask: radial-gradient(farthest-side, transparent calc(100% - 1.6vmin), #000 calc(100% - 1.5vmin));
}

.present-seconds {
	font-size: 6vmin;
	font-weight: 800;
	line-height: 1;
	font-variant-numeric: tabular-nums;
}

/* Under five seconds the dial stops being information and starts being pressure. */
.present-clock[data-urgent] .present-dial {
	background:
		conic-gradient(var(--c-bad) calc(var(--progress, 0) * 360deg), var(--c-surface-2) 0);
}

.present-clock[data-urgent] .present-seconds {
	color: var(--c-bad);
	animation: present-pulse 1s ease-in-out infinite;
}

@keyframes present-pulse {
	50% { transform: scale(1.12); }
}

.present-clock-done {
	flex: none;
	font-size: var(--fs-lg);
	font-weight: 700;
	color: var(--c-muted);
}

/* Reading time: the question alone on the wall, with the tiles absent from the document rather
   than hidden. They are the bottom half of this layout, so what is left spreads into the room they
   would have taken — the prompt grows, and the clock stands beside it instead of over it. */
.present-reading .present-prompt {
	font-size: calc(var(--prompt-fs, 6vmin) * 1.2);
}

.present-reading .present-middle {
	justify-content: center;
	gap: var(--sp-8);
}

/* A calmer dial than the question's: this clock is not a deadline anybody has to beat. */
.present-clock-reading .present-dial {
	background:
		conic-gradient(var(--c-muted) calc(var(--progress, 0) * 360deg), var(--c-surface-2) 0);
}

.present-clock-reading .present-seconds {
	color: var(--c-muted);
}

/* The tiles fill the lower half, four across on a wide screen and two across when there are more
   than four of them. */
.present-tiles {
	display: grid;
	flex: none;
	grid-template-columns: repeat(auto-fit, minmax(min(38vmin, 100%), 1fr));
	gap: var(--sp-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

.present-tile {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	border-radius: var(--r-md);
	background: var(--tile-color);
	color: #fff;
	transition: transform var(--duration) ease-out, opacity var(--duration) ease-out;
}

.present-tile-face {
	display: flex;
	flex: 1;
	gap: var(--sp-3);
	align-items: center;
	padding: var(--sp-3) var(--sp-4);
	min-height: 9vmin;
}

.present-tile .tile-shape {
	flex: none;
	width: 4vmin;
	height: 4vmin;
	fill: #fff;
}

.present-tile-image {
	flex: none;
	height: 8vmin;
	border-radius: var(--r-sm);
	object-fit: cover;
}

.present-tile-label {
	flex: 1;
	min-width: 0;
	font-size: var(--fs-lg);
	font-weight: 700;
	line-height: 1.15;
	overflow-wrap: anywhere;
}

.present-tile-mark {
	flex: none;
	font-size: var(--fs-xl);
	font-weight: 800;
	line-height: 1;
}

/* At the reveal the right answers grow and the wrong ones step back, so the room reads the result
   from the shape of the grid before it reads a word of it. */
.present-tile-right {
	transform: scale(1.03);
	box-shadow: 0 0 0 0.6vmin var(--c-ok);
}

.present-tile-wrong {
	opacity: 0.45;
}

.present-bar {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	padding: var(--sp-1) var(--sp-3);
	background: rgb(0 0 0 / 0.35);
	font-size: var(--fs-sm);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.present-bar-fill {
	position: absolute;
	inset: 0 auto 0 0;
	width: var(--bar, 0%);
	background: rgb(255 255 255 / 0.3);
	transition: width calc(var(--duration) * 3) ease-out;
}

.present-bar-value {
	position: relative;
}

/* --- ordering reveal --- */

.present-sequences {
	display: flex;
	flex: none;
	flex-wrap: wrap;
	gap: var(--sp-6);
	justify-content: center;
}

.present-sequence {
	min-width: 0;
}

.present-sequence-label {
	margin: 0 0 var(--sp-2);
	color: var(--c-muted);
	font-size: var(--fs-sm);
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

.present-sequence-wrong .present-steps {
	opacity: 0.55;
}

.present-steps {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.present-step {
	display: flex;
	gap: var(--sp-2);
	align-items: center;
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-sm);
	background: var(--tile-color);
	color: #fff;
	font-size: var(--fs-sm);
	font-weight: 700;
}

.present-step .tile-shape {
	width: 2.2vmin;
	height: 2.2vmin;
	fill: #fff;
}

.present-step-label {
	overflow: hidden;
	max-width: 24vmin;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.present-step-rank {
	font-variant-numeric: tabular-nums;
	opacity: 0.75;
}

.present-explanation {
	flex: none;
	margin: 0;
	font-size: var(--fs-md);
	text-align: center;
	text-wrap: balance;
}

/* --- leaderboard --- */

.present-board,
.present-podium {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-4);
	min-height: 0;
}

.present-heading {
	margin: 0;
	font-size: var(--fs-xl);
	font-weight: 800;
	text-align: center;
}

/* Rows are placed by rank rather than stacked in order: the element for a player is the same one
   from one leaderboard to the next, so overtaking is a change of offset the browser can animate
   rather than a row appearing somewhere else. */
.present-rows {
	position: relative;
	flex: none;
	height: calc(var(--rows, 5) * 11vmin);
	margin: 0 auto;
	padding: 0;
	width: min(100%, 120vmin);
	list-style: none;
}

.present-row {
	position: absolute;
	inset: 0 0 auto 0;
	display: flex;
	gap: var(--sp-4);
	align-items: center;
	height: 9.5vmin;
	padding: 0 var(--sp-6);
	border-radius: var(--r-md);
	background: var(--c-surface);
	transform: translateY(calc(var(--row, 0) * 11vmin));
	transition: transform calc(var(--duration) * 4) ease-in-out;
}

.present-row-rank {
	flex: none;
	min-width: 2ch;
	color: var(--c-muted);
	font-size: var(--fs-lg);
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

.present-row-name {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	font-size: var(--fs-lg);
	font-weight: 700;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.present-row-gain {
	flex: none;
	color: var(--c-ok);
	font-size: var(--fs-md);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.present-row-delta {
	flex: none;
	color: var(--c-bad);
	font-size: var(--fs-md);
	font-weight: 700;
}

.present-row-up {
	color: var(--c-ok);
}

.present-row-score {
	flex: none;
	font-size: var(--fs-lg);
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

/* --- podium --- */

.present-blocks {
	display: flex;
	flex: 1;
	gap: var(--sp-4);
	align-items: flex-end;
	justify-content: center;
	margin: 0;
	padding: 0;
	list-style: none;
	min-height: 0;
}

.present-block {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	align-items: center;
	justify-content: flex-end;
	order: var(--order, 1);
	width: 26vmin;
	height: calc(var(--block, 1) * 100%);
	padding: var(--sp-4) var(--sp-3);
	border-radius: var(--r-md) var(--r-md) 0 0;
	background: linear-gradient(to bottom, var(--c-brand-soft), var(--c-surface));
	text-align: center;
}

.present-block-name {
	width: 100%;
	overflow: hidden;
	font-size: var(--fs-lg);
	font-weight: 800;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.present-block-score {
	font-size: var(--fs-md);
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
}

.present-block-stand {
	font-size: var(--fs-xl);
	font-weight: 800;
	color: var(--c-brand);
	line-height: 1;
}

.present-podium-foot {
	display: flex;
	flex: none;
	gap: var(--sp-6);
	align-items: flex-end;
	justify-content: space-between;
	max-height: 32vmin;
}

/* Everybody below third place. It is the one region allowed to scroll, and only within itself. */
.present-rest {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(34vmin, 100%), 1fr));
	gap: var(--sp-2);
	flex: 1;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-y: auto;
	max-height: 100%;
}

.present-rest-row {
	display: flex;
	gap: var(--sp-3);
	align-items: center;
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-sm);
	background: var(--c-surface);
	font-size: var(--fs-sm);
}

.present-rest-row .present-row-rank,
.present-rest-row .present-row-name,
.present-rest-row .present-row-score {
	font-size: var(--fs-sm);
}

.present-results {
	flex: none;
	margin: 0;
	text-align: center;
}

.present-results img {
	display: block;
	width: 20vmin;
	height: 20vmin;
	padding: var(--sp-2);
	border-radius: var(--r-sm);
	background: #fff;
}

.present-results figcaption {
	margin-top: var(--sp-2);
	color: var(--c-muted);
	font-size: var(--fs-sm);
}

.present-gone {
	align-items: center;
	justify-content: center;
	text-align: center;
}

/* A room at 720p has less than half the pixels of one at 1080p; the lobby stops being two columns
   before the PIN stops being legible. */
@media (max-aspect-ratio: 5 / 4) {
	.present-lobby {
		grid-template-columns: 1fr;
		grid-template-areas: "join" "qr" "roster";
		justify-items: center;
		text-align: center;
	}

	.present-qr {
		justify-self: center;
	}

	.present-middle {
		flex-direction: column;
	}
}

@media (prefers-reduced-motion: reduce) {
	.present-chip,
	.present-clock[data-urgent] .present-seconds {
		animation: none;
	}

	.present-row,
	.present-tile,
	.present-bar-fill {
		transition: none;
	}
}

/* --- join ---------------------------------------------------------------- */

/* The public front door. It is the one screen in the application designed for a thumb held at
   arm's length in a dark room: one field, one button, nothing else on the page. */
.join {
	display: flex;
	flex: 1;
	flex-direction: column;
	justify-content: center;
	gap: var(--sp-6);
	width: min(26rem, 100%);
	margin-inline: auto;
	text-align: center;
}

.join-brand {
	font-size: var(--fs-xl);
	font-weight: 800;
	letter-spacing: -0.02em;
}

.join-card {
	padding: var(--sp-6);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	box-shadow: var(--shadow-1);
	text-align: start;
}

/* A PIN is read off a wall and typed with a thumb: the digits are as large as the field allows,
   and spaced so a mistyped one is visible without counting. */
.join-pin {
	font-size: var(--fs-xl);
	font-weight: 800;
	text-align: center;
	letter-spacing: 0.25em;
	text-indent: 0.25em;
	font-variant-numeric: tabular-nums;
}

.join-quiz {
	color: var(--c-muted);
	font-weight: 600;
}

.join-foot {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-2);
}

/* --- the player's phone -------------------------------------------------- */

.play {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-3);
	min-height: 0;
}

/* The connection banner is switched by a signal but revealed by this animation: `both` holds it
   invisible for the first three seconds, so a phone that changes cell tower and reconnects never
   shows it at all. */
.play-offline {
	padding: var(--sp-2) var(--sp-4);
	border-radius: var(--r-pill);
	background: var(--c-bad);
	color: #fff;
	font-weight: 600;
	text-align: center;
	animation: play-offline 200ms ease 3s both;
}

@keyframes play-offline {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

.play-stage {
	display: flex;
	flex: 1;
	flex-direction: column;
	min-height: 0;
}

.play-center {
	display: flex;
	flex: 1;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--sp-2);
	text-align: center;
}

.play-title {
	font-size: var(--fs-xl);
	font-weight: 800;
	line-height: 1.1;
	overflow-wrap: anywhere;
}

.play-lede {
	font-size: var(--fs-lg);
}

.play-spinner {
	width: 2.5rem;
	height: 2.5rem;
	border: 4px solid var(--c-surface-2);
	border-top-color: var(--c-brand);
	border-radius: 50%;
	animation: play-spin 900ms linear infinite;
}

@keyframes play-spin {
	to {
		transform: rotate(360deg);
	}
}

.play-cta {
	width: 100%;
	min-height: 56px;
	font-size: var(--fs-lg);
}

.play-leave {
	margin-top: var(--sp-6);
}

/* --- answering ----------------------------------------------------------- */

.play-answers {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-2);
	min-height: 0;
}

/* The countdown, driven by the --progress countdown.js writes every frame — the same property and
   the same script as the projector's dial. Only a transform changes, so a smooth bar costs no
   layout, and the fragment around it never has to be patched. */
.play-timer {
	flex: none;
	height: 6px;
	border-radius: var(--r-pill);
	background: var(--c-surface-2);
	overflow: hidden;
}

.play-timer-fill {
	display: block;
	height: 100%;
	background: var(--c-brand);
	transform-origin: left center;
	transform: scaleX(var(--progress, 1));
}

/* Under five seconds the bar stops being information and starts being pressure. */
.play-timer[data-urgent] .play-timer-fill {
	background: var(--c-bad);
}

.play-hint {
	flex: none;
	color: var(--c-muted);
	font-size: var(--fs-sm);
	text-align: center;
}

/* The reading delay on the phone: the same bar the open question runs, pinned to the top of the
   stage so it does not jump when the tiles replace this screen, and nothing at all to tap — the
   options never reached this device. */
.play-reading {
	position: relative;
	padding-top: var(--sp-3);
}

.play-reading .play-timer {
	position: absolute;
	top: 0;
	right: 0;
	left: 0;
}

/* The question repeated on the phone, for the quizzes that asked for it. Its size comes from
   playPromptStyle, which shrinks it as it grows: the tiles below are what the player came for, and
   they must stay reachable without scrolling. */
.play-prompt {
	flex: none;
	margin: 0;
	font-size: var(--play-prompt-fs, 1.35rem);
	font-weight: 800;
	line-height: 1.2;
	text-align: center;
	text-wrap: balance;
}

/* An image answer keeps the tile's text where it is and sits beside it, so a tile with a picture
   and a tile without are still the same shape. */
.tile-image {
	flex: none;
	width: 3.5rem;
	height: 3.5rem;
	border-radius: var(--r-sm);
	object-fit: cover;
	background: rgb(255 255 255 / 0.2);
}

.play-actions {
	flex: none;
	display: flex;
	align-items: center;
	gap: var(--sp-2);
}

.play-actions .btn:last-child {
	flex: 1;
	min-height: 56px;
	font-size: var(--fs-lg);
}

.play-progress {
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

/* --- what was answered --------------------------------------------------- */

.play-chips {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--sp-2);
	list-style: none;
}

.play-chip {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-2);
	max-width: 100%;
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-md);
	background: var(--tile-color);
	color: #fff;
	font-weight: 700;
}

.play-chip .tile-shape {
	flex: none;
	width: 1.4em;
	height: 1.4em;
	fill: currentcolor;
}

.play-chip-label {
	min-width: 0;
	overflow-wrap: anywhere;
}

.play-chip-image {
	width: 2.5rem;
	height: 2.5rem;
	border-radius: var(--r-sm);
	object-fit: cover;
}

/* --- the reveal ---------------------------------------------------------- */

.play-verdict {
	--verdict: var(--c-muted);
	gap: var(--sp-4);
	padding: var(--sp-4);
	border-radius: var(--r-lg);
	background: color-mix(in srgb, var(--verdict) 12%, transparent);
}

.play-right {
	--verdict: var(--c-ok);
}

.play-wrong {
	--verdict: var(--c-bad);
}

.play-verdict .play-title {
	color: var(--verdict);
}

.play-gain {
	font-size: var(--fs-display);
	font-weight: 800;
	line-height: 1;
	color: var(--verdict);
	font-variant-numeric: tabular-nums;
}

.play-answer {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-2);
}

.play-answer-label {
	color: var(--c-muted);
	font-size: var(--fs-sm);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

.play-standing {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-1);
}

.play-score {
	font-size: var(--fs-xl);
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

.play-rank {
	font-size: var(--fs-lg);
	font-weight: 600;
}

.play-gap {
	color: var(--c-muted);
	font-size: var(--fs-sm);
}

/* A phone turned on its side has half the height and twice the width: the tiles stay the tallest
   thing on the screen, everything else gives way. */
@media (max-height: 30rem) {
	.play-title {
		font-size: var(--fs-lg);
	}

	.play-gain {
		font-size: var(--fs-xl);
	}

	.tile {
		font-size: var(--fs-md);
		padding: var(--sp-2);
	}

	.tile-image {
		width: 2.5rem;
		height: 2.5rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.play-spinner {
		animation: none;
	}
}

/* --- history & reports ---------------------------------------------------- */

.report-list {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.report-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--sp-3);
	padding: var(--sp-3) var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

.report-row > .stack {
	flex: 1 1 16rem;
	min-width: 0;
}

/* The figure strip: one number each, sized so five of them still fit a laptop without wrapping. */
.report-figures {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.report-figure {
	display: flex;
	flex-direction: column;
	gap: var(--sp-1);
	padding: var(--sp-3) var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

.report-figure-value {
	font-size: var(--fs-xl);
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	line-height: 1.1;
}

.report-figure-label {
	color: var(--c-muted);
	font-size: var(--fs-sm);
}

.report-hardest {
	margin: 0;
	padding: var(--sp-3) var(--sp-4);
	border-left: 4px solid var(--c-bad);
	border-radius: var(--r-sm);
	background: var(--c-surface-2);
}

.report-item {
	display: flex;
	flex-direction: column;
	gap: var(--sp-3);
	padding: var(--sp-4);
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
}

.report-item-position {
	flex: none;
	width: 2rem;
	color: var(--c-muted);
	font-size: var(--fs-lg);
	font-variant-numeric: tabular-nums;
	font-weight: 800;
}

.report-item-prompt {
	margin: 0;
	font-size: var(--fs-lg);
}

.report-item-stats {
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.report-options {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* Four columns and a bar underneath: shape, label, count, then the bar spanning the row. The
   correct choice is outlined as well as ticked — colour alone never carries the meaning. */
.report-option {
	display: grid;
	grid-template-columns: auto 1fr auto;
	gap: var(--sp-1) var(--sp-2);
	align-items: center;
	padding: var(--sp-2) var(--sp-3);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
}

.report-option-correct {
	border-color: var(--c-ok);
	background: color-mix(in srgb, var(--c-ok) 8%, transparent);
}

.report-option .tile-shape {
	width: 1rem;
	height: 1rem;
}

.report-option-label {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.report-option-count {
	color: var(--c-muted);
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
}

.report-bar {
	grid-column: 1 / -1;
	height: 6px;
	border-radius: var(--r-pill);
	background: var(--c-surface-2);
}

.report-bar-fill {
	display: block;
	width: var(--bar, 0%);
	height: 100%;
	border-radius: inherit;
	background: var(--c-brand);
	transition: width var(--duration) ease-out;
}

.report-option-correct .report-bar-fill {
	background: var(--c-ok);
}

/* The per-player table. It is a grid rather than a <table> because every row opens: the summary is
   the row, and the answers unfold underneath it. */
.report-table {
	display: flex;
	flex-direction: column;
	border: 1px solid var(--c-border);
	border-radius: var(--r-lg);
	background: var(--c-surface);
	overflow: hidden;
}

.report-head,
.report-player-row {
	display: grid;
	grid-template-columns: 4rem minmax(6rem, 1fr) 6rem 6rem 8rem;
	gap: var(--sp-2);
	align-items: center;
	padding: var(--sp-2) var(--sp-4);
}

.report-head {
	border-bottom: 1px solid var(--c-border);
	background: var(--c-surface-2);
}

.report-col {
	overflow: hidden;
	text-overflow: ellipsis;
	font-variant-numeric: tabular-nums;
}

a.report-col {
	display: flex;
	align-items: center;
	min-height: var(--tap);
	color: var(--c-muted);
	font-size: var(--fs-sm);
	font-weight: 600;
	text-decoration: none;
}

a.report-col:hover {
	color: var(--c-text);
	text-decoration: underline;
}

a.report-col[aria-current="true"] {
	color: var(--c-brand);
}

.report-player + .report-player {
	border-top: 1px solid var(--c-border);
}

/* `display: grid` on a <summary> already drops the disclosure triangle; `list-style` is what keeps
   Firefox from drawing it back. The row is the whole affordance. */
.report-player-row {
	min-height: var(--tap);
	list-style: none;
	cursor: pointer;
}

.report-player-row:hover {
	background: var(--c-surface-2);
}

.report-rank {
	font-size: var(--fs-lg);
	font-weight: 800;
}

.report-name {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	min-width: 0;
	font-weight: 600;
}

.report-answers {
	display: flex;
	flex-direction: column;
	gap: var(--sp-1);
	margin: 0;
	padding: 0 var(--sp-4) var(--sp-3) var(--sp-4);
	list-style: none;
}

.report-answer {
	display: grid;
	grid-template-columns: 2rem minmax(6rem, 1fr) minmax(6rem, 1fr) 5rem 4rem;
	gap: var(--sp-2);
	align-items: center;
	padding: var(--sp-1) var(--sp-2);
	border-left: 3px solid var(--c-border);
	border-radius: var(--r-sm);
	background: var(--c-surface-2);
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
}

.report-answer-right {
	border-left-color: var(--c-ok);
}

.report-answer-wrong {
	border-left-color: var(--c-bad);
}

.report-answer-missing {
	border-left-color: var(--c-border);
	color: var(--c-muted);
}

.report-answer-index {
	color: var(--c-muted);
	font-weight: 700;
}

.report-answer-prompt,
.report-answer-value {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.report-answer-prompt {
	color: var(--c-muted);
}

.report-answer-time,
.report-answer-points {
	text-align: end;
}

/* A phone reading a report drops the two columns that are only ever glanced at. */
@media (max-width: 40rem) {
	.report-head,
	.report-player-row {
		grid-template-columns: 3rem 1fr 5rem;
	}

	.report-head > :nth-child(n + 4),
	.report-player-row > :nth-child(n + 4) {
		display: none;
	}

	.report-answer {
		grid-template-columns: 2rem 1fr 4rem;
	}

	.report-answer-prompt,
	.report-answer-time {
		display: none;
	}
}

/* --- the pairing question ------------------------------------------------ */

/* Two columns, and a number as the only notation between them. A left tile carries the number of
   the preset element it has been bound to; the list underneath is numbered to match. Drawing a
   line between the two would need a layout engine of its own, would not survive a phone rotating,
   and would be invisible from the back of a room — a number is legible everywhere. */

.option-bind {
	flex: none;
	color: var(--c-muted);
	font-weight: 800;
}

.play-binds {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--sp-2);
	min-height: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-y: auto;
}

.play-bind .tile {
	width: 100%;
	min-height: 3.5rem;
}

/* The tile being bound is the one the next tap from the list lands on, so it has to be obvious. */
.play-bind .tile[aria-pressed="true"] {
	outline: 3px solid var(--c-fg);
	outline-offset: 2px;
}

.play-preset {
	display: flex;
	flex: none;
	flex-wrap: wrap;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.play-preset-chip {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-2);
	max-width: 100%;
	min-height: 44px;
	padding: var(--sp-2) var(--sp-3);
	border: 2px solid var(--c-border);
	border-radius: var(--r-pill);
	background: var(--c-surface);
	color: inherit;
	font: inherit;
	font-weight: 700;
	cursor: pointer;
}

/* An element already bound stays tappable — tapping it again moves it — but reads as spent. */
.play-preset-chip[aria-pressed="true"] {
	border-color: var(--c-brand);
	background: var(--c-surface-2);
	color: var(--c-muted);
}

.play-preset-chip[disabled] {
	opacity: 0.5;
	cursor: default;
}

.play-preset-number,
.present-preset-number,
.present-binding-number,
.host-binding-number {
	display: inline-flex;
	flex: none;
	align-items: center;
	justify-content: center;
	min-width: 1.7em;
	height: 1.7em;
	border-radius: var(--r-pill);
	background: var(--c-brand);
	color: #fff;
	font-variant-numeric: tabular-nums;
	font-weight: 800;
}

.play-preset-label {
	min-width: 0;
	overflow-wrap: anywhere;
}

.play-preset-image {
	width: 2.2rem;
	height: 2.2rem;
	border-radius: var(--r-sm);
	object-fit: cover;
}

.play-chips-bind {
	flex-direction: column;
	align-items: stretch;
}

.play-chip-bind {
	flex: none;
	opacity: 0.8;
}

.play-chip-match {
	min-width: 0;
	margin-left: auto;
	overflow-wrap: anywhere;
	font-weight: 800;
}

/* The projector's half: the preset list under the tiles, and — at the reveal — the pairing spelled
   out, one line per pair, with how much of the room got each one. */
.present-preset {
	display: flex;
	flex: none;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--sp-2);
	margin: var(--sp-3) 0 0;
	padding: 0;
	list-style: none;
}

.present-preset-item {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3);
	border: 2px solid var(--c-border);
	border-radius: var(--r-pill);
	background: var(--c-surface);
	font-size: var(--fs-lg);
	font-weight: 700;
}

.present-preset-image {
	width: 3rem;
	height: 3rem;
	border-radius: var(--r-sm);
	object-fit: cover;
}

.present-bindings,
.host-bindings {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: var(--sp-3) 0 0;
	padding: 0;
	list-style: none;
}

.present-binding,
.host-binding {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3);
	border-radius: var(--r-md);
	background: var(--tile-color);
	color: #fff;
	font-weight: 700;
}

.present-binding .tile-shape,
.host-binding .tile-shape {
	flex: none;
	width: 1.4em;
	height: 1.4em;
	fill: currentcolor;
}

.present-binding-label,
.present-binding-match,
.host-binding-label,
.host-binding-match {
	min-width: 0;
	overflow-wrap: anywhere;
}

.present-binding-count,
.host-binding-count {
	margin-left: auto;
	font-variant-numeric: tabular-nums;
	opacity: 0.85;
}

/* --- the presenter-scored item ------------------------------------------- */

/* The control panel's scoring surface: one row per player, each with the amount they are about to
   be given. The number field is a real input rather than a signal-bound one — the panel is patched
   by the stream on every award, and a bound value would be rewritten under the host's cursor. */
.host-awards {
	display: flex;
	flex-direction: column;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.host-award {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-2);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: var(--c-surface);
}

/* A player already scored reads as done, so the host can work down the room without counting. */
.host-award-given {
	border-color: var(--c-ok);
	background: var(--c-surface-2);
}

.host-award-name {
	min-width: 0;
	overflow-wrap: anywhere;
	font-weight: 700;
}

.host-award-input {
	width: 7rem;
	flex: none;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.host-award-total {
	flex: none;
	min-width: 4rem;
	text-align: right;
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
}

/* The projector: the instruction, and the points arriving. Watching a score being given is the
   whole event of this kind of item — there is no clock and no distribution to draw. */
.present-manual {
	display: flex;
	flex: 1;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--sp-4);
	min-height: 0;
	text-align: center;
}

.present-manual-body {
	max-width: 60ch;
	margin: 0;
	color: var(--c-muted);
	font-size: var(--fs-lg);
}

.present-awards {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--sp-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.present-award {
	display: inline-flex;
	align-items: baseline;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-4);
	border-radius: var(--r-pill);
	background: var(--c-surface-2);
	font-size: var(--fs-lg);
	font-weight: 700;
}

.present-award-gain {
	color: var(--c-ok);
	font-variant-numeric: tabular-nums;
}

.present-award-score {
	color: var(--c-muted);
	font-variant-numeric: tabular-nums;
}
