/* ==========================================================================
   Error pages

   The status code is drawn as a mosaic of real book covers: every lit cell of
   the 5x7 digit glyph in libs/errorCodeGlyphs.js becomes one cover. Blurhash
   paints the whole shape immediately, then the covers resolve in place.

   Sizing: the tile width is the only thing that scales. Everything else is a
   multiple of it, so the mosaic keeps its proportions from a 320px phone to an
   ultrawide desktop. --width-scale / --height-scale come from the server and
   already account for the digit count and the gap ratios below; keep the 0.18,
   1.1 and 1.5 here in sync with GAP_RATIO, DIGIT_GAP_RATIO and TILE_ASPECT.
   ========================================================================== */

.error-page {
	--pad: clamp(14px, 4vw, 56px);
	--scrollbar-allowance: 18px;
	/* Space reserved for the heading, subline and actions; the mosaic gets what
	   is left. Sized for a two-line heading so a longer message (401, or any
	   future wording) can never push the mosaic off the bottom of the page. */
	--headroom: 25.5rem;

	--tile-by-width: calc((100vw - var(--pad) * 2 - var(--scrollbar-allowance)) * var(--width-scale));
	--tile-by-height: calc((100vh - var(--headroom)) * var(--height-scale));

	/* Fallback for browsers without min()/clamp() (Safari below 13.4, which this
	   project still targets): size the mosaic off the width alone. It has to be
	   a genuinely computable value — an unsupported clamp() stored in a custom
	   property is not discarded at parse time, it invalidates whatever uses it,
	   which would leave .glyph with no grid tracks at all. */
	--tile: calc(60vw * var(--width-scale));

	box-sizing: border-box;
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1.5rem;
	padding: 2rem 16px;
	text-align: center;
}

@supports (width: clamp(1px, 2vw, 3px)) {
	.error-page {
		--tile: clamp(7px, min(var(--tile-by-width), var(--tile-by-height)), 64px);
		gap: clamp(1.5rem, 5vh, 2.5rem);
		padding: clamp(2rem, 6vh, 3rem) var(--pad);
	}
}

/* Small-viewport units keep the mosaic clear of mobile browser chrome. */
@supports (height: 100svh) {
	.error-page {
		--tile-by-height: calc((100svh - var(--headroom)) * var(--height-scale));
		min-height: 100svh;
	}
}

/* --------------------------------------------------------------- headline */

.error-head {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.7rem;
	max-width: 44rem;
}

.error-head h1 {
	margin: 0;
	font-family: var(--lora);
	font-weight: 600;
	font-size: 2.25rem;
	font-size: clamp(1.5rem, 1.05rem + 2.2vw, 3rem);
	line-height: 1.14;
	letter-spacing: -0.015em;
	color: var(--font-color);
	text-wrap: balance;
}

.error-head p {
	margin: 0;
	max-width: 40ch;
	font-size: 1rem;
	font-size: clamp(0.9rem, 0.85rem + 0.3vw, 1.0625rem);
	line-height: 1.55;
	color: var(--muted-text);
	text-wrap: balance;
}

/* ----------------------------------------------------------------- mosaic */

.glyph-row {
	display: flex;
	align-items: flex-start;
	justify-content: center;
	gap: calc(var(--tile) * 1.1);
	max-width: 100%;
}

.glyph {
	flex: none;
	display: grid;
	grid-template-columns: repeat(var(--cols), var(--tile));
	grid-template-rows: repeat(var(--rows), calc(var(--tile) * 1.5));
	gap: calc(var(--tile) * 0.18);
}

.glyph-tile {
	position: relative;
	z-index: 1;
	display: block;
	transform: translate(var(--dx), var(--dy)) rotate(var(--rot));
	transition: transform 400ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.glyph-tile .cover {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
	overflow: hidden;
	border-radius: calc(var(--tile) * 0.1);
	background: var(--surface-2);
	/* The ring is what keeps a pale cover from dissolving into the page — the
	   digit has to stay readable whatever the books happen to look like. */
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.18);
	transition: box-shadow 400ms ease;
	animation: glyph-tile-in 560ms cubic-bezier(0.2, 0.9, 0.25, 1) both;
	animation-delay: calc(var(--i) * 9ms);
}

.glyph-tile img,
.glyph-tile .blurhash-canvas {
	position: absolute;
	top: 0;
	left: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: inherit;
}

.glyph-tile .blurhash-canvas {
	z-index: 2;
	pointer-events: none;
	transition: opacity 450ms ease;
}

.glyph-tile .cover.loaded .blurhash-canvas {
	opacity: 0;
}

@keyframes glyph-tile-in {
	from {
		opacity: 0;
		transform: scale(0.55);
	}
	to {
		opacity: 1;
		transform: scale(1);
	}
}

/* Lifting a cover out of the digit is the one flourish on this page — it only
   makes sense where there is a real pointer to hover with. */
@media (hover: hover) and (pointer: fine) {
	.glyph-tile:hover {
		z-index: 6;
		transform: translate(var(--dx), var(--dy)) rotate(0deg) scale(1.45);
	}

	.glyph-tile:hover .cover {
		box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.3);
	}
}

/* Shown only when the library can't be reached, so the page still says 404. */
.glyph-fallback {
	font-family: var(--lora);
	font-size: 22vw;
	font-size: clamp(6rem, 22vw, 16rem);
	font-weight: 700;
	line-height: 0.9;
	color: rgba(var(--rgb-primary), 0.1);
}

/* ---------------------------------------------------------------- actions */

.error-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0.9rem 1.75rem;
}

/* Matches the sidebar's active nav item (main.css, `aside nav ul li.active`):
   same 6% wash, same radius, same icon weight. Because both are expressed in
   --rgb-primary / --primary, the dark theme inverts here exactly as it does
   there, with no separate dark rule. */
.error-home-link {
	display: inline-flex;
	align-items: center;
	padding: 10px 30px;
	border-radius: 10px;
	font-size: 1rem;
	font-weight: 500;
	background: rgba(var(--rgb-primary), 0.06);
	color: var(--primary);
	transition: 300ms;
}

.error-home-link i {
	margin-right: 12px;
	color: rgba(var(--rgb-primary), 0.6);
	transition: color 300ms;
}

.error-home-link:hover {
	background: rgba(var(--rgb-primary), 0.1);
}

.error-home-link:hover i {
	color: var(--primary);
}

.error-page a:focus-visible {
	outline: 2px solid var(--primary);
	outline-offset: 3px;
	border-radius: 5px;
}

/* ------------------------------------------------------------ adaptations */

/* --headroom is how much vertical space the text and buttons need; whatever is
   left goes to the mosaic. These queries run narrowest-last on purpose, so the
   most constrained viewport always has the final say. */

/* Short but wide, e.g. a netbook or a half-height desktop window. */
@media screen and (max-height: 720px) {
	.error-page {
		--headroom: 23.5rem;
	}
}

/* Less standing room for the headline and buttons, so the mosaic keeps more. */
@media screen and (max-width: 900px) {
	.error-page {
		--headroom: 21rem;
	}
}

@media screen and (max-width: 600px) {
	.error-page {
		gap: 1.5rem;
	}

}

/* Landscape phones: almost no vertical room, so shrink the text side instead. */
@media screen and (max-height: 560px) and (orientation: landscape) {
	.error-page {
		--headroom: 10.5rem;
		gap: 1.25rem;
		padding-top: 1.25rem;
		padding-bottom: 1.25rem;
	}

	.error-head h1 {
		font-size: 1.4rem;
		font-size: clamp(1.15rem, 0.9rem + 1.4vw, 1.75rem);
	}

	.error-head p {
		display: none;
	}
}

html[data-theme='dark'] .glyph-tile .cover {
	box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 2px 6px rgba(0, 0, 0, 0.5);
}

html[data-theme='dark'] .glyph-tile:hover .cover {
	box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14), 0 10px 28px rgba(0, 0, 0, 0.65);
}

@media (prefers-reduced-motion: reduce) {
	.glyph-tile,
	.glyph-tile .cover,
	.glyph-tile .blurhash-canvas {
		animation: none;
		transition: none;
	}

	.glyph-tile:hover {
		transform: translate(var(--dx), var(--dy)) rotate(var(--rot));
	}
}

