/*
	DDBlockNetwork.

	The palette is taken from the map the video behind the page shows: midnight
	navy for the sky, slate for the blocks, and the moon's cyan. The one warm
	accent is reserved for the thing you are meant to act on -- a button, the
	live count, your own row in a table -- so it never competes with itself.

	Everything is one page, so the layout primitives (panel, table, tee, tabs)
	are defined once and reused by every section rather than each section
	bringing its own.
*/

:root {
	--sky-0: #070d18;
	--sky-1: #0b1524;
	--sky-2: #101d31;
	--slate-1: #1a2c46;
	--slate-2: #1e3050;
	--line: #2a4166;

	--ink: #e8eefc;
	--ink-dim: #a4b6d6;
	--ink-faint: #7489ae;

	--accent: #ff2f6e;
	--accent-soft: #ff5f8d;
	--cyan: #56c8ec;
	--gold: #f5c451;
	--green: #46d39a;

	--radius: 14px;
	--radius-sm: 9px;
	--gap: 1.25rem;

	--shadow: 0 18px 40px -20px rgb(0 0 0 / 80%);
	--mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
	--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
	margin: 0;
	min-height: 100vh;
	background: var(--sky-1);
	color: var(--ink);
	font: 15px/1.6 var(--sans);
	-webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; line-height: 1.15; font-weight: 750; letter-spacing: -0.02em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }

/* ------------------------------------------------------------- backdrop -- */

/*
	The map, playing behind the page.

	Fixed rather than scrolling with the content: it is a window onto the game,
	not a hero image. The veil is what keeps text readable over it -- a plain
	dark overlay flattens the video, so it is a gradient that leaves the middle
	of the frame visible and buries the edges where the text sits.
*/
.backdrop { position: fixed; inset: 0; z-index: -1; overflow: hidden; background: var(--sky-0); }

.backdrop-video {
	width: 100%; height: 100%;
	object-fit: cover;
	opacity: 0.46;
	transform: scale(1.06);
	filter: saturate(1.1) contrast(1.1) brightness(1.15);
}

.backdrop-veil {
	position: absolute; inset: 0;
	background:
		radial-gradient(120% 90% at 50% 10%, transparent 0%, rgb(7 13 24 / 45%) 65%, var(--sky-0) 100%),
		linear-gradient(180deg, rgb(11 21 36 / 45%) 0%, rgb(9 17 29 / 78%) 100%);
}

@media (prefers-reduced-motion: reduce) {
	.backdrop-video { display: none; }
	html { scroll-behavior: auto; }

	/*
		Someone who asked for less motion meant all of it, not just the video: the
		entrance fades, the hover lifts and the bars growing into place are motion
		too. They are decoration in every case, so they simply do not happen.
	*/
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
}

/* ----------------------------------------------------------------- gate -- */

.gate {
	position: fixed; inset: 0; z-index: 60;
	display: grid; place-items: center;
	padding: 1.5rem;
	background: rgb(7 13 24 / 72%);
	backdrop-filter: blur(14px);
	animation: fade 0.35s ease;
}

.gate-panel {
	width: min(28rem, 100%);
	padding: 2rem;
	border: 1px solid var(--line);
	border-radius: calc(var(--radius) + 4px);
	background: linear-gradient(180deg, #1a2c46, #131f33);
	box-shadow: var(--shadow);
	animation: rise 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.gate-brand { text-align: center; margin-bottom: 1.75rem; }

/*
	The heading *is* the logo: the image carries the name, so there is no text
	copy of it underneath. Given room to be read rather than squeezed into a
	corner, since the door is the one screen with nothing else on it.
*/
.gate-mark { margin: 0 0 0.75rem; }
.gate-mark img { width: min(260px, 68%); height: auto; margin: 0 auto; }
.gate-tagline { color: var(--ink-dim); font-size: 0.9rem; }

.gate-tabs { margin-bottom: 1.25rem; }

.gate-form { display: grid; gap: 0.85rem; }

.gate-form label { display: grid; gap: 0.35rem; }
.gate-form label > span { font-size: 0.78rem; color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.08em; }
.gate-form small { color: var(--ink-faint); font-size: 0.78rem; }

.gate-note {
	margin-top: 1.1rem; padding-top: 1.1rem;
	border-top: 1px solid var(--line);
	color: var(--ink-faint); font-size: 0.82rem; text-align: center;
}
.gate-note code { font-family: var(--mono); color: var(--cyan); }

.gate-guest {
	display: block; width: 100%; margin-top: 0.75rem;
	padding: 0.5rem; border: 0; background: none;
	color: var(--ink-faint); font-size: 0.82rem; cursor: pointer;
}
.gate-guest:hover { color: var(--ink-dim); text-decoration: underline; }

input[type="text"], input[type="password"], input[type="search"], input:not([type]) {
	padding: 0.65rem 0.8rem;
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	background: rgb(7 13 24 / 60%);
	transition: border-color 0.15s, box-shadow 0.15s;
}
input:focus-visible {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px rgb(255 47 110 / 18%);
}

.form-error {
	padding: 0.55rem 0.7rem;
	border-radius: var(--radius-sm);
	background: rgb(255 47 110 / 12%);
	border: 1px solid rgb(255 47 110 / 35%);
	color: var(--accent-soft);
	font-size: 0.85rem;
}
.form-error.is-ok {
	background: rgb(70 211 154 / 12%);
	border-color: rgb(70 211 154 / 35%);
	color: var(--green);
}

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

.btn {
	display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
	padding: 0.62rem 1.15rem;
	border: 1px solid transparent;
	border-radius: 999px;
	font-weight: 650; font-size: 0.92rem;
	cursor: pointer;
	transition: transform 0.12s, background 0.15s, border-color 0.15s;
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-soft); }

.btn-ghost { background: rgb(255 255 255 / 6%); border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { background: rgb(255 255 255 / 11%); }

.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.82rem; }

/* ----------------------------------------------------------------- tabs -- */

.tabs {
	display: inline-flex; gap: 0.25rem;
	padding: 0.28rem;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: rgb(7 13 24 / 55%);
}
.tabs button {
	padding: 0.42rem 1rem;
	border: 0; border-radius: 999px;
	background: none; color: var(--ink-dim);
	font-weight: 620; font-size: 0.88rem; cursor: pointer;
	transition: background 0.15s, color 0.15s;
}
.tabs button:hover { color: var(--ink); }
.tabs button.is-active { background: var(--accent); color: #fff; }

.gate-tabs { display: flex; width: 100%; }
.gate-tabs button { flex: 1; }

/* --------------------------------------------------------------- topbar -- */

.shell { display: flex; flex-direction: column; min-height: 100vh; animation: fade 0.4s ease; }

.topbar {
	position: sticky; top: 0; z-index: 30;
	display: flex; align-items: center; gap: clamp(0.5rem, 1.5vw, 1.25rem);
	padding: 0.7rem clamp(1rem, 4vw, 2.5rem);
	border-bottom: 1px solid var(--line);
	background: rgb(10 18 31 / 94%);
	backdrop-filter: blur(12px);
}

/*
	Nothing in the bar may wrap onto a second line: a five-item nav plus the
	search field is wide enough that the log-in button started folding in half
	at laptop widths once Tournament was added. The nav and the search each give
	ground -- the nav by tightening its padding, the search by shrinking -- and
	the brand and the buttons, which have nowhere useful to go, keep their size.
*/
.topbar > * { min-width: 0; }
.brand, .topbar-right, .account, .account .btn { flex-shrink: 0; }

/*
	The wordmark, sized by height so its 4:1 ratio decides the width. Rendered
	well under its natural 1024px, which is what keeps the outline crisp on a
	high-density screen.
*/
.brand { display: flex; align-items: center; flex-shrink: 0; }
.brand img { height: 30px; width: auto; transition: filter 0.2s, transform 0.2s; }
.brand:hover img { filter: drop-shadow(0 0 10px rgb(0 136 235 / 45%)); transform: translateY(-1px); }

.nav { display: flex; gap: 0.15rem; margin-left: 0.5rem; min-width: 0; overflow: hidden; }
.nav a {
	display: flex; align-items: center; gap: 0.4rem;
	padding: 0.45rem clamp(0.5rem, 1.1vw, 0.85rem); border-radius: 999px;
	color: var(--ink-dim); font-weight: 600; font-size: 0.92rem;
	white-space: nowrap;
	transition: background 0.15s, color 0.15s;
}
.nav a:hover { color: var(--ink); background: rgb(255 255 255 / 6%); }
.nav a.is-active { color: var(--ink); background: rgb(255 255 255 / 9%); }

.pill {
	min-width: 1.4rem; padding: 0.05rem 0.4rem;
	border-radius: 999px;
	background: var(--accent); color: #fff;
	font-size: 0.72rem; font-weight: 700; text-align: center;
}
.pill.is-zero { background: rgb(255 255 255 / 12%); color: var(--ink-faint); }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 0.75rem; }

.search { position: relative; min-width: 0; }
.search input { width: clamp(8rem, 18vw, 16rem); }

.search-results {
	position: absolute; top: calc(100% + 0.4rem); right: 0;
	width: min(22rem, 80vw);
	padding: 0.35rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: var(--slate-1); box-shadow: var(--shadow);
	max-height: 22rem; overflow: auto;
}
.search-results button {
	display: flex; align-items: center; gap: 0.6rem; width: 100%;
	padding: 0.45rem 0.55rem; border: 0; border-radius: var(--radius-sm);
	background: none; text-align: left; cursor: pointer;
}
.search-results button:hover { background: rgb(255 255 255 / 7%); }
.search-results .group { padding: 0.4rem 0.55rem 0.2rem; color: var(--ink-faint); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.09em; }
.search-results .empty { padding: 0.8rem; color: var(--ink-faint); font-size: 0.86rem; text-align: center; }

.account { display: flex; align-items: center; gap: 0.55rem; }
.account .who { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.35rem; border-radius: 999px; }
.account .who:hover { background: rgb(255 255 255 / 7%); }
.account .name { font-weight: 650; font-size: 0.9rem; }
.account .lvl { color: var(--ink-faint); font-size: 0.78rem; font-family: var(--mono); }

.nav-toggle { display: none; }

/* ----------------------------------------------------------------- main -- */

main { flex: 1; width: min(78rem, 100%); margin: 0 auto; padding: clamp(1.25rem, 3vw, 2.5rem) clamp(1rem, 4vw, 2.5rem) 4rem; }

.view { animation: rise 0.35s cubic-bezier(0.2, 0.8, 0.2, 1); }

.view-head { margin-bottom: 1.5rem; }
.view-head h2 { font-size: clamp(1.5rem, 3vw, 2rem); }
.view-head p { color: var(--ink-dim); margin-top: 0.3rem; }

.eyebrow {
	font-family: var(--mono); font-size: 0.78rem;
	letter-spacing: 0.14em; text-transform: uppercase; color: var(--cyan);
}

/* ----------------------------------------------------------------- hero -- */

.hero {
	display: grid; grid-template-columns: 1.25fr 1fr; gap: var(--gap); align-items: center;
	margin-bottom: var(--gap);
	padding: clamp(1.5rem, 4vw, 2.75rem);
	border: 1px solid var(--line); border-radius: calc(var(--radius) + 4px);
	background: linear-gradient(135deg, rgb(26 44 70 / 94%), rgb(19 31 51 / 90%));
	box-shadow: var(--shadow);
}
.hero h2 { font-size: clamp(1.9rem, 5vw, 3rem); margin: 0.5rem 0 0.75rem; }
.lede { color: var(--ink-dim); max-width: 34rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.4rem; }

.hero-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
.stat {
	padding: 0.9rem 1rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: rgb(7 13 24 / 45%);
}
.stat .k { display: block; font-size: 1.5rem; font-weight: 750; letter-spacing: -0.03em; }
.stat .l { color: var(--ink-faint); font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.08em; }
.stat.is-live .k { color: var(--green); }

/* ---------------------------------------------------------------- grids -- */

.grid { display: grid; gap: var(--gap); margin-bottom: var(--gap); }
.grid-servers { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.grid-split { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }

/* --------------------------------------------------------------- panels -- */

.panel {
	border: 1px solid var(--line); border-radius: var(--radius);
	background: rgb(19 34 56 / 94%);
	overflow: hidden;
}
.panel-head {
	display: flex; align-items: center; justify-content: space-between; gap: 1rem;
	padding: 0.9rem 1.1rem;
	border-bottom: 1px solid var(--line);
}
.panel-head h3 { font-size: 1rem; }
.panel-head a { color: var(--cyan); font-size: 0.84rem; font-weight: 600; }
.panel-head a:hover { text-decoration: underline; }
.panel-body { padding: 0.5rem; }

/* -------------------------------------------------------------- servers -- */

.server {
	display: flex; flex-direction: column; gap: 0.75rem;
	padding: 1.1rem;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: rgb(19 34 56 / 94%);
	transition: transform 0.16s, border-color 0.16s;
}
.server:hover { transform: translateY(-2px); border-color: var(--slate-2); }

.server.is-down { opacity: 0.72; }

.server-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.server-title { min-width: 0; }
.server h3 { font-size: 1.05rem; }
.server .tagline { color: var(--ink-dim); font-size: 0.86rem; }

/*
	The map, shown only when it is not the server's own name again.

	Kept small and labelled by its own styling rather than by a word: on the one
	server where it differs it reads as "V3ROYAL / blmapV3ROYAL", which is the
	relationship without spending a line on saying so.
*/
.server-map {
	display: block;
	margin-top: 0.15rem;
	font-family: var(--mono);
	font-size: 0.72rem;
	color: var(--ink-faint);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* The population, which is what a server list is scanned for. */
.server-gauge { display: grid; gap: 0.4rem; }

.server-count {
	display: flex;
	align-items: baseline;
	gap: 0.35rem;
	font-family: var(--mono);
	color: var(--ink-faint);
	font-size: 0.78rem;
}

.server-count b {
	font-family: var(--sans);
	font-size: 1.45rem;
	font-weight: 750;
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--ink);
}

.server-slots { font-size: 0.85rem; }
.server-word { margin-left: auto; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem; }

.dot { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--ink-faint); }
.dot::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); }
.dot.is-up { color: var(--green); }
.dot.is-up::before { background: var(--green); box-shadow: 0 0 0 3px rgb(70 211 154 / 18%); }
.dot.is-down { color: var(--accent); }
.dot.is-down::before { background: var(--accent); }

.server-meta { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; color: var(--ink-faint); font-size: 0.8rem; font-family: var(--mono); }

.server-bar { height: 5px; border-radius: 999px; background: rgb(7 13 24 / 70%); overflow: hidden; }
.server-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--cyan), var(--accent)); transition: width 0.4s ease; }

.copy-addr {
	display: flex; align-items: center; gap: 0.45rem;
	width: 100%;
	padding: 0.4rem 0.6rem;
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	background: rgb(7 13 24 / 55%);
	font-family: var(--mono); font-size: 0.8rem; color: var(--ink-dim);
	cursor: pointer;
	transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.copy-addr:hover { color: var(--ink); border-color: var(--cyan); background: rgb(7 13 24 / 80%); }
.copy-addr-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The affordance, kept quiet until the card is under the cursor. */
.copy-addr-hint {
	margin-left: auto;
	font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.1em;
	color: var(--ink-faint);
	opacity: 0;
	transition: opacity 0.15s;
}
.server:hover .copy-addr-hint, .copy-addr:focus-visible .copy-addr-hint { opacity: 1; }

/* ------------------------------------------------------------------ tee -- */

/*
	A tee. Drawing one is tee-skin-renderer's job (vendored at
	/css/vendor/tee-skin-renderer.css, linked in index.html) -- it owns the
	.tee box model, its 96em-square sizing, and everything about cutting the
	skin sheet apart. All that is left to set here is how big each one is.

	The vendored stylesheet scales off font-size (96em at font-size:1px = 96px),
	so a target pixel size is that size divided by 96 -- written as a division
	rather than the pre-computed decimal so the actual pixel target this size
	class reserves stays visible in the source.
*/
.tee.xs { font-size: calc(26px / 96); }
.tee.sm { font-size: calc(34px / 96); }
.tee.md { font-size: calc(52px / 96); }
.tee.lg { font-size: calc(92px / 96); }

/* --------------------------------------------------------------- tables -- */

.rows { display: flex; flex-direction: column; }

.row {
	display: grid; align-items: center; gap: 0.75rem;
	grid-template-columns: 2.2rem 34px 1fr auto;
	padding: 0.5rem 0.6rem;
	border-radius: var(--radius-sm);
	transition: background 0.12s;
}
.row:hover { background: rgb(255 255 255 / 5%); }
.row.is-me { background: rgb(255 47 110 / 10%); box-shadow: inset 2px 0 0 var(--accent); }

.row .rank { font-family: var(--mono); font-size: 0.85rem; color: var(--ink-faint); text-align: right; }
.row .rank.top1 { color: var(--gold); font-weight: 700; }
.row .rank.top2 { color: #cfd8e6; font-weight: 700; }
.row .rank.top3 { color: #d99a63; font-weight: 700; }

.row .who { min-width: 0; }
.row .who b { display: block; font-weight: 650; font-size: 0.94rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .who small { color: var(--ink-faint); font-size: 0.78rem; }

.row .figs { display: flex; gap: 1.1rem; font-family: var(--mono); font-size: 0.84rem; }
.row .figs span { color: var(--ink-dim); }
.row .figs b { color: var(--ink); font-weight: 650; }

.row-link { cursor: pointer; }

.board { margin-top: 1rem; }
.board .rows { border: 1px solid var(--line); border-radius: var(--radius); background: rgb(19 34 56 / 94%); padding: 0.4rem; }

.board-head {
	display: grid; align-items: center; gap: 0.75rem;
	grid-template-columns: 2.2rem 34px 1fr auto;
	padding: 0.3rem 0.6rem 0.6rem;
	color: var(--ink-faint); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.09em;
}

/* -------------------------------------------------------- online players -- */

.server-group { margin-bottom: var(--gap); }
.server-group .panel-head .dot { font-size: 0.76rem; }

.players { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: 0.5rem; padding: 0.5rem; }

.player {
	display: flex; align-items: center; gap: 0.65rem;
	padding: 0.55rem 0.65rem;
	border: 1px solid transparent; border-radius: var(--radius-sm);
	background: rgb(7 13 24 / 40%);
	cursor: pointer;
	transition: border-color 0.15s, transform 0.15s;
}
.player:hover { border-color: var(--slate-2); transform: translateY(-1px); }
.player .who { min-width: 0; }
.player b { display: block; font-size: 0.9rem; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.player small { color: var(--ink-faint); font-size: 0.76rem; }
.player .spec { color: var(--ink-faint); font-size: 0.72rem; font-family: var(--mono); }

.empty-state { padding: 2.5rem 1rem; text-align: center; color: var(--ink-faint); }
.empty-state b { display: block; color: var(--ink-dim); margin-bottom: 0.3rem; }

/* ----------------------------------------------------------------- news -- */

.news { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }

.news-card {
	display: flex; flex-direction: column;
	border: 1px solid var(--line); border-radius: var(--radius);
	background: rgb(19 34 56 / 94%);
	overflow: hidden;
}
.news-card .body { padding: 1.1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.news-card .meta { display: flex; align-items: center; gap: 0.6rem; }
.news-card h3 { font-size: 1.05rem; }
/*
	The description sits inside the card with the heading it belongs to. On the
	old site it was a sibling of the image and overlapped the card below.
*/
.news-card p { color: var(--ink-dim); font-size: 0.9rem; }

.tag {
	padding: 0.15rem 0.55rem; border-radius: 999px;
	background: rgb(86 200 236 / 14%); color: var(--cyan);
	font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
}
.when { color: var(--ink-faint); font-size: 0.78rem; font-family: var(--mono); }

/* -------------------------------------------------------------- profile -- */

.profile-head {
	display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem;
	padding: clamp(1.25rem, 3vw, 2rem);
	border: 1px solid var(--line); border-radius: calc(var(--radius) + 4px);
	background: linear-gradient(135deg, rgb(26 44 70 / 94%), rgb(19 31 51 / 90%));
	margin-bottom: var(--gap);
}
.profile-head .id { flex: 1; min-width: 12rem; }
.profile-head h2 { font-size: clamp(1.4rem, 3vw, 2rem); }
.profile-head .sub { color: var(--ink-dim); margin-top: 0.25rem; }

.badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.6rem; }
.badge {
	padding: 0.2rem 0.6rem; border-radius: 999px;
	border: 1px solid var(--line); background: rgb(7 13 24 / 50%);
	font-size: 0.76rem; color: var(--ink-dim);
}
.badge.is-vip { border-color: rgb(245 196 81 / 45%); color: var(--gold); }
.badge.is-online { border-color: rgb(70 211 154 / 45%); color: var(--green); }

.xp { margin-top: 0.9rem; }
.xp-bar { height: 7px; border-radius: 999px; background: rgb(7 13 24 / 70%); overflow: hidden; }
.xp-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--cyan), var(--accent)); transition: width 0.5s ease; }
.xp-text { display: flex; justify-content: space-between; margin-top: 0.35rem; color: var(--ink-faint); font-size: 0.78rem; font-family: var(--mono); }

.figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: 0.6rem; margin-bottom: var(--gap); }

/* ---------------------------------------------------------------- toast -- */

.toast {
	position: fixed; left: 50%; bottom: 1.5rem; z-index: 80;
	transform: translateX(-50%);
	padding: 0.6rem 1.1rem;
	border: 1px solid var(--line); border-radius: 999px;
	background: var(--slate-1); box-shadow: var(--shadow);
	font-size: 0.88rem;
	animation: rise 0.25s ease;
}

/* --------------------------------------------------------------- footer -- */

.site-foot {
	display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem;
	padding: 1.25rem clamp(1rem, 4vw, 2.5rem);
	border-top: 1px solid var(--line);
	color: var(--ink-faint); font-size: 0.84rem;
}
.site-foot nav { display: flex; gap: 1rem; }
.site-foot a:hover { color: var(--ink); }

/* ------------------------------------------------------------- skeleton -- */

.skeleton { border-radius: var(--radius-sm); background: linear-gradient(90deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 9%), rgb(255 255 255 / 4%)); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
.skeleton-row { height: 46px; margin: 0.3rem; }

@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ------------------------------------------------------------ responsive -- */

@media (max-width: 60rem) {
	.hero { grid-template-columns: 1fr; }
	.search input { width: 10rem; }
}

/*
	The nav folds into the toggle earlier than the rest of the layout does.

	Five inline items plus the brand, the search field and the buttons need
	about 1060px before anything has to give; below that the nav ran into the
	search box instead of stopping. This is a wider breakpoint than the tables
	below want, so the two are separate rather than sharing one number.
*/
@media (max-width: 70rem) {
	.nav-toggle {
		display: grid; gap: 4px; place-content: center;
		width: 38px; height: 38px; padding: 0 9px;
		border: 1px solid var(--line); border-radius: var(--radius-sm);
		background: none; cursor: pointer;
	}
	.nav-toggle span { display: block; width: 20px; height: 2px; border-radius: 2px; background: var(--ink); }

	.topbar { flex-wrap: wrap; }
	.nav {
		order: 3; width: 100%; margin: 0.5rem 0 0; overflow-x: auto;
		padding-bottom: 0.25rem;
	}
	.nav[hidden] { display: none; }
	.topbar-right { order: 2; }
}

@media (max-width: 48rem) {
	.search input { width: 8.5rem; }
	.row { grid-template-columns: 1.8rem 30px 1fr; }
	.row .figs { grid-column: 1 / -1; padding-left: 4.3rem; }
	.board-head { display: none; }
}

/* -------------------------------------------------------------- discord -- */

/*
	The community band on the front page.

	Discord's own iframe used to sit here, in Discord's colours and at Discord's
	fixed width. This is the same information drawn as part of the site: the
	blurple appears only as a wash behind the panel, so it reads as Discord
	without fighting the palette.
*/
.discord {
	display: grid;
	grid-template-columns: minmax(0, 1.5fr) auto auto;
	align-items: center;
	gap: var(--gap);
	margin-bottom: var(--gap);
	padding: 1.15rem 1.35rem;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background:
		radial-gradient(30rem 12rem at 8% -40%, rgb(88 101 242 / 24%), transparent 70%),
		linear-gradient(180deg, var(--sky-2), var(--sky-1));
	box-shadow: var(--shadow);
}

.discord-brand { display: flex; align-items: center; gap: 0.9rem; min-width: 0; }

.discord-icon {
	width: 56px; height: 56px;
	flex-shrink: 0;
	border-radius: 16px;
	border: 1px solid var(--line);
	background: var(--sky-0);
	object-fit: cover;
}

.discord h3 { font-size: 1.1rem; }
.discord-desc { color: var(--ink-dim); font-size: 0.85rem; margin-top: 0.2rem; }

.discord-figures { display: flex; gap: 1.75rem; }
.discord-figure { text-align: right; }
.discord-figure b { display: block; font-size: 1.5rem; font-weight: 750; line-height: 1; letter-spacing: -0.02em; }
.discord-figure span { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--ink-faint); }

/* The live count gets the one moving thing in the panel. */
.discord-figure.is-live b { color: var(--green); }
.discord-figure.is-live b::before {
	content: '';
	display: inline-block;
	width: 0.5rem; height: 0.5rem;
	margin-right: 0.4rem;
	vertical-align: 0.15em;
	border-radius: 50%;
	background: var(--green);
	animation: discord-pulse 2.4s ease-in-out infinite;
}

@keyframes discord-pulse {
	0%, 100% { box-shadow: 0 0 0 0 rgb(70 211 154 / 55%); }
	60% { box-shadow: 0 0 0 0.45rem rgb(70 211 154 / 0%); }
}

.discord-side { display: flex; align-items: center; gap: 0.9rem; }

/*
	The avatar row, which only appears once Server Settings -> Widget is enabled
	on the Discord itself; the API refuses the member list until then.
*/
.discord-faces { display: flex; }

.discord-face {
	width: 32px; height: 32px;
	margin-left: -0.5rem;
	display: grid; place-items: center;
	border-radius: 50%;
	border: 2px solid var(--sky-2);
	background: var(--slate-1);
	font-size: 0.75rem; font-weight: 700; color: var(--ink-dim);
	overflow: hidden;
}

.discord-face:first-child { margin-left: 0; }
.discord-face img { width: 100%; height: 100%; object-fit: cover; }
.discord-face.is-idle { filter: saturate(0.5); }
.discord-face.is-dnd { border-color: var(--accent); }

@media (max-width: 60rem) {
	.discord { grid-template-columns: 1fr; }
	.discord-figures { gap: 2.5rem; }
	.discord-figure { text-align: left; }
	.discord-side { flex-wrap: wrap; }
	.discord-join { width: 100%; justify-content: center; }
}

/* ----------------------------------------------------------- tournament -- */

.pill-soon {
	background: rgb(245 196 81 / 16%);
	color: var(--gold);
	border: 1px solid rgb(245 196 81 / 30%);
}

/*
	"Coming soon", as something worth arriving at.

	Everything animates on `.is-playing`, which the view adds when it opens, so
	the sequence plays each time someone navigates here rather than only on the
	first load. Nothing here is load-bearing: with animation turned off it is a
	heading, a bracket and a link, which is the same page standing still.
*/
.soon {
	position: relative;
	display: grid;
	justify-items: center;
	gap: 1.1rem;
	padding: clamp(2rem, 6vw, 4rem) 1.25rem clamp(2.5rem, 7vw, 4.5rem);
	text-align: center;
	overflow: hidden;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: linear-gradient(180deg, var(--sky-2), var(--sky-1) 60%);
}

/* A slow wash behind the whole thing, so the panel is never quite static. */
.soon-glow {
	position: absolute;
	inset: -40% -20% auto;
	height: 130%;
	background:
		radial-gradient(28rem 16rem at 30% 20%, rgb(255 47 110 / 18%), transparent 65%),
		radial-gradient(26rem 15rem at 72% 35%, rgb(86 200 236 / 16%), transparent 65%);
	animation: soon-drift 14s ease-in-out infinite alternate;
	pointer-events: none;
}

@keyframes soon-drift {
	from { transform: translate3d(-3%, -2%, 0) scale(1); }
	to { transform: translate3d(4%, 3%, 0) scale(1.08); }
}

.soon > *:not(.soon-glow) { position: relative; }

.soon-eyebrow { display: inline-flex; align-items: center; gap: 0.5rem; color: var(--gold); }

.soon-dot {
	width: 0.45rem; height: 0.45rem;
	border-radius: 50%;
	background: var(--gold);
	animation: discord-pulse 2s ease-in-out infinite;
	box-shadow: 0 0 0 0 rgb(245 196 81 / 55%);
}

/*
	The title, with a sheen that crosses it.

	The gradient is painted on the text itself and the ::after copy rides over
	it -- clipping a moving highlight to the glyphs rather than drawing a bar
	across the panel, which would cut through everything behind it too.
*/
.soon-title {
	font-size: clamp(2.4rem, 9vw, 5rem);
	letter-spacing: -0.03em;
	line-height: 1;
	background: linear-gradient(180deg, var(--ink), var(--ink-faint));
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.soon-title::after {
	content: attr(data-text);
	position: absolute;
	inset: 0;
	background: linear-gradient(100deg,
		transparent 35%, rgb(255 255 255 / 85%) 50%, transparent 65%);
	background-size: 300% 100%;
	background-position: 120% 0;
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.soon.is-playing .soon-title::after { animation: soon-sheen 3.6s ease-in-out 0.6s infinite; }

@keyframes soon-sheen {
	0% { background-position: 120% 0; }
	45%, 100% { background-position: -60% 0; }
}

.soon-lede { max-width: 34rem; color: var(--ink-dim); }
.soon-note { color: var(--ink-faint); font-size: 0.85rem; }

/* --- the bracket --- */

.bracket {
	position: relative;
	display: grid;
	grid-template-columns: auto 120px auto;
	align-items: center;
	gap: 0.75rem;
	margin: 0.75rem 0 0.5rem;
}

/*
	The lines column is stretched to exactly the height of the seats beside it,
	so the y positions in the viewBox land on seat centres whatever the seats
	end up measuring -- a fixed height only lines up at one font size.
*/
.bracket-seats, .bracket-lines { align-self: stretch; }

.bracket-seats { display: grid; gap: 0.55rem; }

.seat {
	display: flex; align-items: center; gap: 0.6rem;
	padding: 0.4rem 0.75rem 0.4rem 0.4rem;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: rgb(7 13 24 / 65%);
	opacity: 0;
	transform: translateX(-0.75rem);
}

.seat b { font-family: var(--mono); font-size: 0.8rem; color: var(--ink-faint); letter-spacing: 0.08em; }

/* Each seat arrives after the one above it, so the round fills top to bottom. */
.soon.is-playing .seat {
	animation: seat-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
	animation-delay: calc(var(--seat) * 0.13s);
}

@keyframes seat-in {
	to { opacity: 1; transform: none; }
}

/*
	The lines, drawn rather than shown.

	Each path is dashed with its own length and offset by it, so it starts fully
	hidden and "draws" as the offset winds back to zero.
*/
.bracket-lines { width: 120px; height: auto; overflow: visible; }

.bracket-lines path {
	fill: none;
	stroke: var(--line);
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-dasharray: 200;
	stroke-dashoffset: 200;
}

.soon.is-playing .bracket-lines path {
	animation: line-draw 0.7s ease-out forwards;
}

.soon.is-playing .bracket-lines path:nth-child(-n+4) { animation-delay: 0.55s; }
.soon.is-playing .bracket-lines path:nth-child(n+5):nth-child(-n+6) { animation-delay: 1.05s; stroke: var(--cyan); }
.soon.is-playing .bracket-lines path:last-child { animation-delay: 1.45s; stroke: var(--accent); }

@keyframes line-draw {
	to { stroke-dashoffset: 0; }
}

.bracket-cup {
	position: relative;
	display: grid; place-items: center;
	width: 4.25rem; height: 4.25rem;
	border-radius: 50%;
	border: 1px solid rgb(245 196 81 / 35%);
	background: radial-gradient(circle at 50% 35%, rgb(245 196 81 / 22%), transparent 70%);
	opacity: 0;
	transform: scale(0.8);
}

.soon.is-playing .bracket-cup {
	animation: cup-in 0.6s cubic-bezier(0.2, 1.3, 0.4, 1) 1.75s forwards;
}

@keyframes cup-in { to { opacity: 1; transform: none; } }

.cup {
	width: 2rem; height: 2rem;
	fill: none; stroke: var(--gold); stroke-width: 1.7;
	stroke-linecap: round; stroke-linejoin: round;
}

.soon.is-playing .cup { animation: cup-breathe 3s ease-in-out 2.4s infinite; }

@keyframes cup-breathe {
	0%, 100% { transform: scale(1); filter: none; }
	50% { transform: scale(1.07); filter: drop-shadow(0 0 8px rgb(245 196 81 / 55%)); }
}

/* One ring leaving the cup, on the same beat as the breathing. */
.bracket-cup-ring {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	border: 1px solid rgb(245 196 81 / 50%);
	opacity: 0;
}

.soon.is-playing .bracket-cup-ring { animation: cup-ring 3s ease-out 2.4s infinite; }

@keyframes cup-ring {
	0% { opacity: 0.8; transform: scale(1); }
	70%, 100% { opacity: 0; transform: scale(1.6); }
}

@media (max-width: 34rem) {
	.bracket { grid-template-columns: auto 70px auto; }
	.bracket-lines { width: 70px; }
	.seat b { display: none; }
}

/*
	Someone who has asked for less movement gets the finished state, not a
	half-played one: everything is where the animation would have left it.
*/
@media (prefers-reduced-motion: reduce) {
	.soon-glow, .soon.is-playing .soon-title::after,
	.soon.is-playing .cup, .soon.is-playing .bracket-cup-ring,
	.discord-figure.is-live b::before, .soon-dot { animation: none !important; }

	.soon .seat, .soon .bracket-cup { opacity: 1; transform: none; animation: none !important; }
	.soon .bracket-lines path { stroke-dashoffset: 0; animation: none !important; }
	.bracket-cup-ring { opacity: 0; }
}

/* The visitor's own seat, marked the way their own row is on a leaderboard. */
.seat.is-you {
	border-color: rgb(255 47 110 / 45%);
	background: rgb(255 47 110 / 10%);
}
.seat.is-you b { color: var(--accent-soft); }

/* ----------------------------------------------------------- news, write -- */

/*
	The compose form, shown only to an account that may post.

	Deliberately plain: it is a tool for one person rather than part of what
	visitors come to look at, so it borrows the panel styling and adds nothing
	of its own beyond the picture preview.
*/
.compose {
	display: grid;
	gap: 0.85rem;
	margin-bottom: var(--gap);
	padding: 1.15rem 1.25rem 1.25rem;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: linear-gradient(180deg, var(--sky-2), var(--sky-1));
}

.compose-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.compose-head h3 { font-size: 1rem; }
.compose-who { color: var(--ink-faint); font-size: 0.8rem; font-family: var(--mono); }

.compose label { display: grid; gap: 0.3rem; }
.compose label > span {
	color: var(--ink-faint); font-size: 0.72rem;
	text-transform: uppercase; letter-spacing: 0.09em;
}
.compose label > span small { text-transform: none; letter-spacing: 0; opacity: 0.8; }

.compose input[type="text"], .compose input:not([type]), .compose textarea {
	width: 100%;
	padding: 0.55rem 0.7rem;
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	background: rgb(7 13 24 / 70%); color: var(--ink);
	font: inherit;
}
.compose textarea { resize: vertical; min-height: 5rem; line-height: 1.55; }
.compose input:focus, .compose textarea:focus { outline: none; border-color: var(--accent); }

.compose-row { display: grid; grid-template-columns: 10rem 1fr; gap: 0.75rem; }
@media (max-width: 34rem) { .compose-row { grid-template-columns: 1fr; } }

.compose input[type="file"] {
	padding: 0.45rem;
	border: 1px dashed var(--line); border-radius: var(--radius-sm);
	background: rgb(7 13 24 / 45%);
	font-size: 0.85rem; color: var(--ink-dim);
	cursor: pointer;
}

.compose-preview {
	position: relative;
	margin: 0;
	border: 1px solid var(--line); border-radius: var(--radius-sm);
	overflow: hidden;
}
.compose-preview img { width: 100%; max-height: 15rem; object-fit: cover; }

.compose-drop {
	position: absolute; top: 0.5rem; right: 0.5rem;
	padding: 0.25rem 0.6rem;
	border: 1px solid var(--line); border-radius: 999px;
	background: rgb(7 13 24 / 85%); color: var(--ink-dim);
	font-size: 0.75rem; cursor: pointer;
}
.compose-drop:hover { color: var(--ink); border-color: var(--accent); }

.compose-actions { display: flex; justify-content: flex-end; }

/* --- what a post looks like once it is up --- */

.news-image { margin: 0; border-bottom: 1px solid var(--line); }
.news-image img { width: 100%; max-height: 20rem; object-fit: cover; }
.news-card .meta .by { color: var(--ink-faint); font-size: 0.76rem; }
.news-card .body > p + p { margin-top: 0.6rem; }

.news-remove {
	margin-left: auto;
	padding: 0.15rem 0.5rem;
	border: 1px solid var(--line); border-radius: 999px;
	background: none; color: var(--ink-faint);
	font-size: 0.72rem; cursor: pointer;
}
.news-remove:hover { color: var(--accent-soft); border-color: var(--accent); }
