/*
 * Chrome, layout and every board rule.
 *
 * The jewels themselves are drawn in js/ui/art.js, as one sheet of SVG symbols.
 * What lives here is where things sit and how they move.
 *
 * Two numbers do most of the work: --cell and --gap, published by BoardView from
 * one measurement of the board. Everything on the board -- a piece's position, a
 * corner radius, how far a blast reaches -- is a calc() off those.
 */

:root {
	--night: #241b3a; /* the deepest ground */
	--velvet: #2f2450; /* panels and the board's backdrop */
	--velvet-lift: #3b2e63; /* raised cells */
	--velvet-sink: #352a59; /* the other half of the checkerboard */

	--gold: #f2c455;
	--gold-deep: #b8791a;
	--cream: #fdf6e6;
	--ink: #2a1f45;

	--text: #efe7ff;
	--text-soft: #b5a6d6;
	--text-faint: #6f6191;

	--rose: #e5344f;

	--radius-cell: 22%;
	--radius-control: 14px;
	--radius-card: 22px;

	--cell: 36px;
	--gap: 4px;

	/* Overwritten at boot from js/ui/timing.js, so a transition and the wait that
	   covers it are always the same number. */
	--ms-swap: 150ms;
	--ms-clear: 220ms;
	--ms-fall: 260ms;
	--ms-activate: 240ms;
	--ms-shuffle: 420ms;
	--move-ms: var(--ms-fall);

	color-scheme: dark;
}

* {
	box-sizing: border-box;
}

html,
body {
	height: 100%;
	margin: 0;
}

body {
	background: var(--night);
	color: var(--text);
	font-family: ui-rounded, "SF Pro Rounded", "Segoe UI Variable Display", system-ui,
		-apple-system, "Helvetica Neue", sans-serif;
	font-size: clamp(0.95rem, 3.4vw, 1.1rem);
	line-height: 1.35;
	/* No rubber-banding and no double-tap zoom: this is a board you swipe across,
	   not a document you scroll. */
	overscroll-behavior: none;
	touch-action: manipulation;
	-webkit-text-size-adjust: 100%;
}

#app {
	position: relative;
	width: 100%;
	max-width: 30rem;
	height: 100%;
	margin-inline: auto;
	background: radial-gradient(120% 60% at 50% 0%, #3a2a63 0%, var(--night) 70%);
	overflow: hidden;
}

.art-sheet {
	position: absolute;
	width: 0;
	height: 0;
}

.screen {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	padding: max(0.75rem, env(safe-area-inset-top)) 0.75rem
		max(0.75rem, env(safe-area-inset-bottom));
}

.screen[hidden] {
	display: none;
}

/* --- Controls ------------------------------------------------------------- */

button {
	min-height: 3rem;
	padding: 0.5rem 1rem;
	border: 2px solid #4b3c7d;
	border-radius: var(--radius-control);
	background: var(--velvet);
	color: var(--text);
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	-webkit-user-select: none;
	user-select: none;
	transition: background-color 90ms ease-out, border-color 90ms ease-out;
}

button.primary {
	min-height: 3.5rem;
	border-color: var(--gold-deep);
	background: linear-gradient(#f7d67d, var(--gold));
	color: var(--ink);
	font-size: 1.05em;
}

@media (hover: hover) {
	button:hover:not(:disabled) {
		border-color: var(--gold-deep);
	}
}

button:active:not(:disabled) {
	translate: 0 1px;
}

button:focus-visible {
	outline: none;
	box-shadow: 0 0 0 3px rgb(242 196 85 / 45%);
}

button:disabled {
	cursor: default;
}

/* --- Map ------------------------------------------------------------------ */

.map-header {
	flex: none;
	text-align: center;
	padding-bottom: 0.5rem;
}

.crest {
	display: block;
	width: 4.5rem;
	height: 4.5rem;
	margin: 0 auto;
	filter: drop-shadow(0 3px 0 rgb(0 0 0 / 30%));
}

.map h1 {
	margin: 0.1rem 0 0;
	font-size: clamp(1.7rem, 7.5vw, 2.2rem);
	font-weight: 800;
	letter-spacing: -0.01em;
}

.subtitle {
	margin: 0.15rem 0 0;
	color: var(--text-soft);
	font-size: 0.9em;
}

.map-header .primary {
	width: 100%;
	margin-top: 0.625rem;
}

/* Three ways onto the ladder, three across a phone. */
.start-at {
	margin-top: 0.5rem;
}

.start-at h2 {
	margin: 0 0 0.375rem;
	color: var(--text-soft);
	font-size: 0.75em;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.difficulty {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 0.5rem;
}

.difficulty button {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.1rem;
	min-height: 3rem;
	padding: 0.4rem 0.375rem;
}

/* The one the player is already playing within. */
.difficulty button[aria-current] {
	border-color: var(--gold);
	background: var(--velvet-lift);
}

/* Two lines under the name rather than one wrapping line: at three buttons to a
   phone's width, "Level 17 · Rows of Crates" breaks wherever it likes. */
.difficulty .level {
	color: var(--text-soft);
	font-size: 0.8em;
	line-height: 1.2;
}

.difficulty .name {
	color: var(--text-faint);
	font-size: 0.65em;
	line-height: 1.2;
	text-wrap: balance;
}

.map-scroll {
	flex: 1;
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	mask-image: linear-gradient(180deg, transparent 0, #000 3rem, #000 calc(100% - 1rem), transparent 100%);
}

/* Nodes are placed by index from the bottom up, so level one is the first thing
   the eye lands on and the trail climbs away from it. */
.map-trail {
	position: relative;
	width: 100%;
}

.node,
.trail-dot {
	position: absolute;
	left: calc(var(--x) * 100%);
	bottom: calc(var(--i) * var(--step) + 3rem);
	translate: -50% 0;
}

.trail-dot {
	width: 0.45rem;
	height: 0.45rem;
	border-radius: 50%;
	background: #4b3c7d;
	/* Lifts the dot's centre onto the line the node centres sit on. */
	margin-bottom: 1.775rem;
}

.node {
	display: grid;
	place-items: center;
	width: 4rem;
	height: 4rem;
	min-height: 0;
	padding: 0;
	border-width: 3px;
	border-radius: 50%;
	background: var(--velvet-lift);
	box-shadow: 0 3px 0 #1b1430;
}

.node-number {
	font-size: 1.3rem;
	font-weight: 800;
	line-height: 1;
}

/* The name sits under the node rather than inside it, so a long one is free to
   be as long as it likes without changing the node's size. */
.node-name {
	position: absolute;
	top: calc(100% + 0.2rem);
	width: 9rem;
	color: var(--text-faint);
	font-size: 0.7rem;
	font-weight: 600;
	text-align: center;
	text-wrap: balance;
}

.node-mark {
	position: absolute;
	right: -0.35rem;
	top: -0.35rem;
	width: 1.5rem;
	height: 1.5rem;
	padding: 0.2rem;
	border-radius: 50%;
	background: var(--gold);
	color: var(--ink);
}

.node.is-cleared {
	border-color: var(--gold-deep);
	background: linear-gradient(#4a3a7d, #3a2c66);
}

.node.is-cleared .node-name {
	color: var(--text-soft);
}

.node.is-current {
	border-color: var(--gold);
	background: linear-gradient(#f7d67d, var(--gold));
	color: var(--ink);
	animation: beacon 1.8s ease-in-out infinite;
}

.node.is-current .node-name {
	color: var(--gold);
}

.node.is-locked {
	border-color: #3a2f5e;
	background: #2a2148;
	color: var(--text-faint);
	box-shadow: none;
}

@keyframes beacon {
	0%, 100% { box-shadow: 0 3px 0 #1b1430, 0 0 0 0 rgb(242 196 85 / 45%); }
	50% { box-shadow: 0 3px 0 #1b1430, 0 0 0 0.7rem rgb(242 196 85 / 0%); }
}

/* A landscape phone, or a short browser window. The crest goes and the title
   shrinks, so the trail keeps some room under the controls. */
@media (max-height: 40rem) {
	.map .crest {
		display: none;
	}

	.map h1 {
		font-size: 1.5rem;
	}
}

/* --- Game screen ---------------------------------------------------------- */

.hud {
	flex: none;
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.corner {
	min-height: 2.5rem;
	padding-inline: 0.75rem;
	font-size: 0.85em;
}

.hud-level {
	flex: 1;
	display: flex;
	flex-direction: column;
	line-height: 1.15;
	text-align: center;
}

.level-label {
	font-weight: 700;
}

.level-name {
	color: var(--text-soft);
	font-size: 0.78em;
}

.moves {
	display: flex;
	flex-direction: column;
	align-items: center;
	min-width: 3.25rem;
	padding: 0.2rem 0.4rem;
	border: 2px solid #4b3c7d;
	border-radius: var(--radius-control);
	background: var(--velvet);
	line-height: 1.05;
}

.moves-value {
	font-size: 1.35rem;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

.moves-label {
	color: var(--text-soft);
	font-size: 0.65rem;
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

.moves.is-low {
	border-color: var(--rose);
	color: #ffd7dd;
}

.goals {
	flex: none;
	display: flex;
	justify-content: center;
	gap: 0.5rem;
	margin: 0.5rem 0 0;
	padding: 0;
	list-style: none;
}

.goal {
	display: flex;
	align-items: center;
	gap: 0.3rem;
	padding: 0.25rem 0.6rem 0.25rem 0.3rem;
	border: 2px solid #4b3c7d;
	border-radius: 999px;
	background: var(--velvet);
}

.goal-art {
	width: 1.9rem;
	height: 1.9rem;
}

.goal-count {
	min-width: 1.1rem;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	text-align: center;
}

.goal-tick {
	width: 1.3rem;
	height: 1.3rem;
	color: var(--gold);
}

.goal.is-done {
	border-color: var(--gold-deep);
	background: #3d3168;
}

/* --- The board ------------------------------------------------------------ */

.board-area {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	min-height: 0;
}

.board {
	position: relative;
	/* Resolved here rather than at :root, so it follows the --cell and --gap the
	   board view measures onto this element. */
	--pitch: calc(var(--cell) + var(--gap));
	border-radius: 1.25rem;
	/* The gesture owns the pointer outright. Without this the browser claims a
	   swipe as a scroll and the move never arrives. */
	touch-action: none;
	-webkit-user-select: none;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
	-webkit-touch-callout: none;
}

.board::before {
	content: "";
	position: absolute;
	inset: calc(var(--gap) * -2);
	border-radius: 1.5rem;
	background: rgb(20 14 38 / 55%);
}

.layer {
	position: absolute;
	inset: 0;
}

/* Both are clipped to the board so a refill rises out of the top edge instead of
   arriving from somewhere above the screen. */
.pieces,
.effects {
	overflow: hidden;
	border-radius: 1.25rem;
}

.effects {
	pointer-events: none;
}

.tile,
.piece,
.obstacle,
.effect {
	position: absolute;
	top: 0;
	left: 0;
	width: var(--cell);
	height: var(--cell);
	translate: calc(var(--x) * var(--pitch)) calc(var(--y) * var(--pitch));
}

.tile {
	border-radius: var(--radius-cell);
	background: var(--velvet-lift);
}

.tile.is-dark {
	background: var(--velvet-sink);
}

.piece {
	transition: translate var(--move-ms) cubic-bezier(0.34, 0.06, 0.4, 1);
	will-change: translate;
	pointer-events: none;
}

.piece .art,
.obstacle .art {
	width: 100%;
	height: 100%;
	display: block;
}

.piece.is-upright .art {
	rotate: 90deg;
}

.piece.is-special {
	filter: drop-shadow(0 0 calc(var(--cell) * 0.16) rgb(255 224 138 / 65%));
}

.piece.is-hinted {
	animation: nudge 0.9s ease-in-out infinite;
}

.piece.is-selected {
	scale: 1.12;
	filter: drop-shadow(0 0 calc(var(--cell) * 0.12) var(--gold));
	transition: translate var(--move-ms) cubic-bezier(0.34, 0.06, 0.4, 1), scale 120ms ease-out;
}

.piece.is-clearing,
.piece.is-merging {
	transition: translate var(--move-ms) linear, scale var(--ms-clear) ease-in,
		opacity var(--ms-clear) ease-in, rotate var(--ms-clear) ease-in;
	opacity: 0;
	scale: 0.25;
}

/* The piece a special is about to replace collapses into the new one rather than
   bursting away from it. */
.piece.is-merging {
	scale: 1.35;
	rotate: 12deg;
}

.piece.is-born {
	animation: born var(--ms-fall) cubic-bezier(0.2, 1.5, 0.5, 1);
}

.piece.is-refused {
	animation: refused var(--ms-swap) ease-in-out;
}

.piece.is-scrambling {
	animation: scramble var(--ms-shuffle) ease-in-out;
}

.obstacle {
	pointer-events: none;
}

.obstacle.is-hit {
	animation: rattle var(--ms-clear) ease-out;
}

.obstacle.is-gone {
	animation: shatter var(--ms-clear) ease-in forwards;
}

@keyframes born {
	from { scale: 0.3; rotate: -25deg; }
	to { scale: 1; rotate: 0deg; }
}

@keyframes nudge {
	0%, 100% { scale: 1; }
	50% { scale: 1.14; }
}

@keyframes refused {
	0%, 100% { rotate: 0deg; }
	30% { rotate: -9deg; }
	70% { rotate: 9deg; }
}

@keyframes scramble {
	0%, 100% { rotate: 0deg; scale: 1; }
	50% { rotate: 180deg; scale: 0.7; }
}

@keyframes rattle {
	0%, 100% { translate: calc(var(--x) * var(--pitch)) calc(var(--y) * var(--pitch)); }
	25% { translate: calc(var(--x) * var(--pitch) - 3px) calc(var(--y) * var(--pitch)); }
	75% { translate: calc(var(--x) * var(--pitch) + 3px) calc(var(--y) * var(--pitch)); }
}

@keyframes shatter {
	from { opacity: 1; scale: 1; }
	to { opacity: 0; scale: 1.4; }
}

/* --- What a special throws off -------------------------------------------- */

.effect {
	pointer-events: none;
}

.effect.is-streak-h,
.effect.is-streak-v {
	background: linear-gradient(90deg, transparent, #fff3c4 45%, #ffd76a 55%, transparent);
	border-radius: 999px;
	animation: streak var(--ms-activate) ease-out forwards;
}

.effect.is-streak-h {
	width: calc(var(--cell) * 3);
	height: calc(var(--cell) * 0.55);
	margin-top: calc(var(--cell) * 0.225);
	margin-left: calc(var(--cell) * -1);
}

.effect.is-streak-v {
	width: calc(var(--cell) * 0.55);
	height: calc(var(--cell) * 3);
	margin-left: calc(var(--cell) * 0.225);
	margin-top: calc(var(--cell) * -1);
	background: linear-gradient(180deg, transparent, #fff3c4 45%, #ffd76a 55%, transparent);
}

@keyframes streak {
	from { opacity: 0.2; scale: 0.2 1; }
	40% { opacity: 1; }
	to { opacity: 0; scale: 9 1; }
}

.effect.is-burst {
	--reach: calc(var(--pitch) * (var(--radius, 2) * 2 + 1));
	width: var(--reach);
	height: var(--reach);
	margin: calc((var(--cell) - var(--reach)) / 2);
	border-radius: 50%;
	background: radial-gradient(circle, #fffdf0 0%, #ffd76a 35%, rgb(229 52 79 / 45%) 65%, transparent 72%);
	animation: burst var(--ms-activate) ease-out forwards;
}

@keyframes burst {
	from { opacity: 0.9; scale: 0.15; }
	to { opacity: 0; scale: 1.05; }
}

.effect.is-full {
	inset: 0;
	width: 100%;
	height: 100%;
	translate: none;
	border-radius: 1.25rem;
	background: radial-gradient(circle at 50% 50%, #ffffff 0%, #e9c9ff 40%, transparent 75%);
	animation: flash var(--ms-activate) ease-out forwards;
}

@keyframes flash {
	from { opacity: 0; }
	30% { opacity: 0.95; }
	to { opacity: 0; }
}

/* --- Status and overlays -------------------------------------------------- */

.status {
	flex: none;
	min-height: 2.4em;
	margin: 0;
	color: var(--gold);
	font-size: 0.82em;
	text-align: center;
	text-wrap: pretty;
}

.overlay {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: grid;
	place-items: center;
	padding: 1.25rem;
	background: rgb(20 14 38 / 72%);
}

.overlay[hidden] {
	display: none;
}

.card {
	display: flex;
	flex-direction: column;
	gap: 0.9rem;
	width: 100%;
	padding: 1.75rem;
	border: 2px solid #4b3c7d;
	border-radius: var(--radius-card);
	background: var(--velvet);
	text-align: center;
	animation: rise 220ms cubic-bezier(0.2, 1.2, 0.4, 1);
}

@keyframes rise {
	from { opacity: 0; translate: 0 1.5rem; }
	to { opacity: 1; translate: 0 0; }
}

.card h2 {
	margin: 0;
	font-size: clamp(1.5rem, 7vw, 2rem);
	font-weight: 800;
}

.card p {
	margin: 0;
	color: var(--text-soft);
}

.card .crest {
	width: 3.5rem;
	height: 3.5rem;
}

/* The UA rule only reaches HTML elements, and the ticks are SVG. */
[hidden] {
	display: none !important;
}

@media (prefers-reduced-motion: reduce) {
	button,
	.piece {
		transition-duration: 1ms;
	}

	.node.is-current,
	.piece.is-hinted {
		animation: none;
	}
}
