/* ============================================================================
   JetBrains Mono, served from the container.
   The UI asked for Lucida Console, then Courier New, then monospace. Lucida Console exists only on
   Windows and is a bitmap-era face with a small x-height; everywhere else the stack fell
   through to Courier New -- or, on Linux, to Liberation Mono, its metric-compatible clone.
   Those are the widest, thinnest, smallest-on-the-body monospace faces in common use, which is
   why the cards read as tiny and squished no matter what size they were set at.
   Self-hosted rather than pulled from Google Fonts: a game server may have no outbound
   internet, and a webfont request would leak every player's visit to a third party.
   JetBrains Mono is SIL OFL 1.1 -- see css/fonts/OFL.txt, which must ship with the files.
   ============================================================================ */
@font-face {
	font-family: "JetBrains Mono";
	src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("fonts/JetBrainsMono-Bold.woff2") format("woff2");
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("fonts/JetBrainsMono-Italic.woff2") format("woff2");
	font-weight: 400;
	font-style: italic;
	font-display: swap;
}

/* Remove white focus rings but preserve element styling */
*:focus {
	outline: none !important;
	box-shadow: none !important;
}

*:focus-visible {
	outline: 1px solid rgba(34, 211, 238, 0.5) !important;
	box-shadow: 0 0 0 2px rgba(34, 211, 238, 0.1) !important;
}

/* Specifically remove focus from main containers */
body:focus,
body:focus-visible,
html:focus,
html:focus-visible,
.container:focus,
.container:focus-visible {
	outline: none !important;
	box-shadow: none !important;
}

:root {
	/* Modern dark theme color palette */
	--bg-primary: #0f172a;      /* Slate 900 - main background */
	--bg-secondary: #1e293b;    /* Slate 800 - cards/panels */
	--bg-tertiary: #334155;     /* Slate 700 - hover states */
	--bg-input: #1e293b;        /* Input backgrounds */

	--text-primary: #f1f5f9;    /* Slate 100 - main text */
	--text-secondary: #94a3b8;  /* Slate 400 - muted text */
	--text-muted: #64748b;      /* Slate 500 - disabled text */

	--accent-primary: #22d3ee;  /* Cyan 400 - primary accent */
	--accent-hover: #67e8f9;    /* Cyan 300 - hover accent */
	--accent-secondary: #a78bfa; /* Violet 400 - secondary accent */
	--accent-secondary-hover: #c4b5fd; /* Violet 300 */

	--success: #4ade80;         /* Green 400 */
	--success-dark: #166534;    /* Green 800 */
	--warning: #fbbf24;         /* Amber 400 */
	--warning-dark: #92400e;    /* Amber 800 */
	--danger: #f87171;          /* Red 400 */
	--danger-dark: #991b1b;     /* Red 800 */
	--info: #38bdf8;            /* Sky 400 */
	--info-dark: #0c4a6e;       /* Sky 900 */

	--border-color: #475569;    /* Slate 600 */
	--border-light: #334155;    /* Slate 700 */

	/* Legacy variables for backwards compatibility */
	--main-background-color: var(--bg-primary);
	--main-font-color: var(--text-primary);
	--main-highlighted-color: var(--accent-primary);
	--outline-light: var(--accent-primary);
	--button-font-color-idle: var(--accent-secondary);
	--button-font-color-hover: var(--success);
	--main-color: var(--accent-secondary);
	--main-alt-color: var(--success);
	--tab-background-color-unfocused: var(--bg-primary);
	--tab-background-color-focused: var(--bg-secondary);
	--tab-font-color-focused: var(--text-primary);
	--tab-font-color-unfocused: var(--text-primary);
	--input-background-color-focused: var(--bg-input);
	--input-background-color-unfocused: var(--bg-input);
	--input-font-color-focused: var(--text-primary);
	--input-font-color-unfocused: var(--text-primary);
	--disabled: var(--text-muted);

	/* Fonts - modern system UI stack */
	--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
	/* The terminal face the whole player UI is set in. The fallbacks are only for the moment
	   before the woff2 lands, and deliberately exclude Courier New / Liberation Mono. */
	--font-mono-ui: "JetBrains Mono", ui-monospace, SFMono-Regular, "Cascadia Mono",
		"Segoe UI Mono", Consolas, "DejaVu Sans Mono", monospace;
}

/* Global scrollbar styling */
::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

::-webkit-scrollbar-track {
	background: var(--bg-secondary);
	border-radius: 5px;
}

::-webkit-scrollbar-thumb {
	background: var(--accent-secondary);
	border-radius: 5px;
	border: 2px solid var(--bg-secondary);
}

::-webkit-scrollbar-thumb:hover {
	background: var(--accent-secondary-hover);
}

::-webkit-scrollbar-corner {
	background: var(--bg-secondary);
}

/* Firefox scrollbar */
* {
	scrollbar-width: thin;
	scrollbar-color: var(--accent-secondary) var(--bg-secondary);
}

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

html {
	font-size: 16px;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

body {
	color: var(--text-primary);
	background: var(--bg-primary);
	font-family: var(--font-sans);
	font-size: 0.9375rem;
	line-height: 1.5;
	margin: 0;
	padding: 1rem;
	min-height: 100vh;
}

/* Typography */
h1, h2, h3, h4, h5, h6 {
	color: var(--text-primary);
	font-weight: 600;
	margin-top: 0;
	margin-bottom: 0.5rem;
}

p {
	margin-top: 0;
	margin-bottom: 1rem;
	font-weight: 400;
}

/* Links */
a {
	color: var(--accent-secondary);
	text-decoration: none;
	transition: color 0.2s ease;
}

a:hover {
	color: var(--success);
	text-decoration: none;
}

/* Keep all links purple (not just visited) */
a:visited {
	color: var(--accent-secondary);
}

a:visited:hover {
	color: var(--success);
}

/* Ensure clickable enabled links are always purple */
a:link {
	color: var(--accent-secondary);
}

/* Custom scrollbar */
::-webkit-scrollbar {
	width: 8px;
	height: 8px;
}

::-webkit-scrollbar-track {
	background: var(--bg-secondary);
	border-radius: 4px;
}

::-webkit-scrollbar-thumb {
	background: var(--border-color);
	border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
	background: var(--accent-primary);
}

/* Form elements */
input, select, textarea {
	font-family: var(--font-sans);
	font-size: 0.9375rem;
	color: var(--text-primary);
	background-color: var(--bg-input);
	border: 1px solid var(--border-color);
	border-radius: 0.375rem;
	padding: 0.5rem 0.75rem;
	transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

input:focus, select:focus, textarea:focus {
	outline: none;
	border-color: var(--accent-primary);
	box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15);
}

input::placeholder, textarea::placeholder {
	color: var(--text-muted);
}

/* Fix checkbox aspect ratio - ensure square shape */
input[type="checkbox"] {
	width: 1em !important;
	height: 1em !important;
	min-width: 1em !important;
	min-height: 1em !important;
	aspect-ratio: 1 / 1 !important;
	flex-shrink: 0;
}

.form-check-input {
	width: 1em !important;
	height: 1em !important;
	min-width: 1em !important;
	min-height: 1em !important;
	aspect-ratio: 1 / 1 !important;
	flex-shrink: 0;
}

/* Fix Bootstrap form-check-input override */
.form-check-input[type="checkbox"] {
	width: 1em !important;
	height: 1em !important;
	border-radius: 0.25em !important;
}

/* Round radio buttons matching dark theme */
input[type="radio"],
.form-check-input[type="radio"] {
	width: 1em !important;
	height: 1em !important;
	min-width: 1em !important;
	min-height: 1em !important;
	max-width: 1em !important;
	max-height: 1em !important;
	aspect-ratio: 1 / 1 !important;
	border-radius: 50% !important;
	border: 2px solid var(--border-color) !important;
	background-color: var(--bg-input) !important;
	background-image: none !important;
	appearance: none !important;
	-webkit-appearance: none !important;
	flex-shrink: 0;
	cursor: pointer;
	vertical-align: middle;
	padding: 0 !important;
	margin-top: 0 !important;
}
input[type="radio"]:checked,
.form-check-input[type="radio"]:checked {
	border-color: var(--accent-primary, #00e6e6) !important;
	background-color: var(--bg-input) !important;
	background-image: none !important;
	box-shadow: inset 0 0 0 3px var(--accent-primary, #00e6e6) !important;
}

/* Dark theme for form-control textareas */
.form-control, textarea.form-control {
	background-color: var(--bg-input) !important;
	color: var(--text-primary) !important;
	border-color: var(--border-color) !important;
}

.form-control:focus, textarea.form-control:focus {
	background-color: var(--bg-input) !important;
	color: var(--text-primary) !important;
	border-color: var(--accent-primary) !important;
	box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15) !important;
}

.form-control:disabled, textarea.form-control:disabled {
	background-color: var(--bg-tertiary) !important;
	color: var(--text-secondary) !important;
	opacity: 0.7;
}

/* Placeholder styling for form controls */
.form-control::placeholder {
	color: var(--text-muted) !important;
	opacity: 1;
}

.form-control::-webkit-input-placeholder {
	color: var(--text-muted) !important;
}

.form-control::-moz-placeholder {
	color: var(--text-muted) !important;
}

.form-control:-ms-input-placeholder {
	color: var(--text-muted) !important;
}

/* Tables */
table {
	border-collapse: collapse;
	width: 100%;
}

th, td {
	padding: 0.75rem;
	text-align: left;
	vertical-align: middle;
}

/* Utility classes */
.sm-font-italic {
	font-size: 0.8125rem;
	font-style: italic;
}

.disabled {
	color: var(--text-muted) !important;
	cursor: not-allowed;
}

.textarea {
	background-color: var(--bg-input);
	color: var(--text-primary);
	border: 1px solid var(--border-color);
	border-radius: 0.375rem;
}

.pri-color {
	color: var(--accent-secondary);
}

.alt-color {
	color: var(--success);
}

/* Mods view link styling */
.mod-view-link {
	color: orchid !important;
	text-decoration: none !important;
	font-weight: bold !important;
}

.mod-view-link .view-text {
	color: palegreen !important;
	font-weight: bold !important;
}

.mod-view-link:hover {
	color: palegreen !important;
}

.mod-view-link:hover .view-text {
	color: orchid !important;
}

/* Mods list in popover */
body:has(.google_header) .popover-body a,
.popover-body a {
	color: orchid !important;
}

body:has(.google_header) .popover-body a:hover,
.popover-body a:hover {
	color: palegreen !important;
}

.highlight-color {
	color: var(--accent-primary);
}

/* Card/Panel styling */
.outline {
	border: 1px solid var(--accent-primary);
	border-radius: 0.5rem;
	background-color: var(--bg-secondary);
}

.bottom_line {
	border-bottom: 1px solid var(--accent-primary);
}

.top_line {
	border-top: 1px solid var(--accent-primary);
}

.right_line_solid {
	border-right: 1px solid var(--accent-primary);
}

/* Buttons */
.sm-bttn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.5rem 1rem;
	font-family: var(--font-sans);
	font-size: 0.875rem;
	font-weight: 500;
	color: var(--accent-secondary);
	background-color: var(--bg-secondary);
	border: 1px solid var(--accent-primary);
	border-radius: 0.375rem;
	cursor: pointer;
	transition: all 0.2s ease;
	text-decoration: none;
	white-space: nowrap;
}

.sm-bttn:hover {
	color: var(--success) !important;
	background-color: var(--bg-tertiary) !important;
	border-color: var(--accent-hover);
}

.sm-bttn:active {
	transform: translateY(1px);
}

.sm-bttn:disabled {
	color: var(--text-muted);
	border-color: var(--border-color);
	cursor: not-allowed;
	opacity: 0.6;
}

/* Layout helpers */
.center {
	margin-left: auto;
	margin-right: auto;
	text-align: center;
}

.align-left {
	text-align: left;
}

.float-left {
	float: left;
}

.align-right {
	text-align: right;
}

.float-right {
	float: right;
}

/* Card components */
.card-panel {
	background-color: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.5rem;
	padding: 1.5rem;
	margin-bottom: 1rem;
}

.card-panel-header {
	color: var(--success);
	font-size: 1rem;
	font-weight: 600;
	padding-bottom: 0.75rem;
	margin-bottom: 1rem;
	border-bottom: 1px solid var(--accent-primary);
}

/* Trophy/icon styling */
.trophy_icon {
	width: 55px;
	margin-left: auto;
	margin-right: auto;
	text-align: center;
	vertical-align: middle;
	filter: brightness(120%);
}

.trophy_icon img {
	width: 36px;
	height: 36px;
}

.trophy_dimmed {
	filter: grayscale(100%) brightness(100%) opacity(25%);
}

/* Client download styling */
.client_download_button {
	text-align: right;
	padding-top: 20px;
	padding-right: 20px;
	font-family: var(--font-sans);
	font-size: 100%;
}

.client_download_os_icon img {
	margin-top: 10px;
	width: 32px;
	height: 32px;
	filter: brightness(75%);
	transition: filter 0.3s ease;
}

.client_download_os_icon:hover img {
	filter: brightness(110%);
}

.client_download_os_icon:active img {
	filter: brightness(120%);
}

.client_download_button_font {
	font-family: var(--font-sans);
	font-weight: normal;
	font-size: 100%;
}

/* Override button font for authenticated page */
body:has(.google_header) .client_download_button_font {
	font-family: var(--font-mono-ui) !important;
	font-weight: normal !important;
	font-size: 100% !important;
}

.client_download_tooltip {
	font-family: var(--font-sans);
	font-weight: normal;
	font-size: 100%;
	background-color: var(--bg-secondary);
	color: var(--success);
	border-color: var(--accent-primary);
	text-align: right;
}

p.client_download_tooltip_otherbuilds {
	font-family: var(--font-sans);
	font-weight: normal;
	font-size: 65%;
	padding-top: 0px;
	display: flex;
	justify-content: center;
	align-items: center;
	margin: 0 auto;
}

p.client_download_tooltip_otherbuilds a {
	color: var(--accent-secondary);
	text-decoration: none;
	font-weight: 300;
	transition: color 0.3s ease;
}

p.client_download_tooltip_otherbuilds:hover a {
	color: var(--success);
}

.versionLabel {
	display: block;
	font-size: 0.83em;
	margin-top: 2px;
	transition: color 0.3s ease;
}

.versionLabel:hover {
	color: var(--success);
}

/* Header styling */
.name_header {
	color: var(--accent-primary);
	text-align: left;
	padding-left: 50px;
	padding-top: 15px;
}

.google_header {
	text-align: left;
	padding-left: 50px;
	padding-top: 30px;
}

.google_header img {
	width: 75px;
	height: 75px;
	border-radius: 50%;
}

/* The player's own access-list ID (V_<steamid64>), under the welcome line. Server owners need
   it to add someone to an access list, and until now the only way to find it was a third-party
   lookup site.

   It sat under the 75px avatar first, where `width: 75px` + `word-break: break-all` split a
   19-character id across three lines. It is inline-block with nowrap now: the box is sized by
   the id rather than the id being crammed into a fixed box. */
.steamid-self {
	/* BLOCK, so it sits on its own line under the welcome text -- inline-block put it on the
	   same line, to the right of it. `width: fit-content` keeps the clickable/underlined box
	   sized to the id rather than spanning the whole header, which a plain block would do. */
	display: block;
	width: fit-content;
	margin-top: 2px;
	font-size: 0.7rem;
	font-weight: normal;
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	color: #ff4fd8;
	cursor: pointer;
	line-height: 1.2;
	white-space: nowrap;
	user-select: all;
	transition: color 0.15s ease;
}

.steamid-self:hover {
	color: #ff8ae6;
	text-decoration: underline;
}

.steamid-self-copied {
	color: #4ade80 !important;
}

.steamid-self-failed {
	color: #f87171 !important;
}

/* World card grid */
.outer {
	height: 100%;
	width: 100%;
	max-height: 100%;
	margin-top: 50px;
}

.inner {
	max-width: 1600px;
	padding: 2rem;
	height: auto;
	max-height: 100%;
	overflow: hidden;
	margin: auto;
	display: flex;
	justify-content: center;
	align-content: center;
}

.wrapper {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: 1.5rem;
	background-color: var(--bg-primary);
	padding: 2rem 0;
	width: 100%;
}

.card_dimmed {
	cursor: not-allowed;
	text-decoration: none;
	pointer-events: none;
	border: 1px dashed var(--text-muted) !important;
	color: var(--text-muted) !important;
	opacity: 0.75;
}

.catbox {
	background-color: var(--bg-secondary);
	border-radius: 0.5rem;
	padding: 1rem;
	padding-bottom: 2rem;
	font-size: 1rem;
	border: 1px solid var(--accent-primary);
}

.card_worldName {
	color: var(--info);
	font-size: 1.125rem;
	font-weight: 600;
	text-align: center;
}

.card_worldLaunch {
	font-size: 0.9375rem;
	text-align: center;
}

a.card_worldLaunch:link,
a.card_worldLaunch:visited {
	color: var(--accent-secondary);
}

a.card_worldLaunch:hover {
	color: var(--success);
}

/* A crossplay world has nothing to launch -- it is joined by code, not by address -- so its
   header is a label rather than an anchor. Styled to read as a heading, deliberately NOT as
   a link: a link-coloured word that does nothing on click is worse than plain text. */
.card_worldLaunch.launch-link-static {
	color: var(--text-secondary);
	cursor: default;
}

/* The join code sits next to a copy action, same shape as the password row above it. */
.vanilla-joincode code {
	color: var(--accent-secondary);
	font-size: 0.8rem;
}
.vanilla-joincode .vanilla-password-action {
	margin-left: 0.4rem;
}

.card_worldInfo {
	color: var(--text-secondary);
	font-size: 0.75rem;
}

/* ============================================
   PUBLIC AUTHENTICATED PAGE - Original Styles
   These override the admin styles for the public page
   ============================================ */

/* Body styles for authenticated page only */
body:has(.google_header) {
	font-family: var(--font-mono-ui) !important;
	font-size: 16px !important;
	/* `normal` is roughly 1.17 for these faces, which is what made the cards read as squished.
	   1.5 is the standard body figure and the usual 1.4-1.6 range for dense rows; unitless so
	   it scales with whatever size each element ends up at. */
	line-height: 1.5 !important;
	margin: 10px !important;
	padding: 5px 30px 0 30px !important;
}

/* All tables on authenticated page */
body:has(.google_header) table {
	border-collapse: separate !important;
	text-indent: initial !important;
	border-spacing: 2px !important;
}

/* Main layout table should be full width */
body:has(.google_header) > table {
	width: 100% !important;
}

/* Inner tables (inside cards) can be auto width */
body:has(.google_header) .catbox table {
	width: auto !important;
}

/* All th/td on authenticated page - remove global padding */
body:has(.google_header) th,
body:has(.google_header) td {
	padding: 0 !important;
	font-weight: normal !important;
	font-family: var(--font-mono-ui) !important;
	border: none !important;
	outline: none !important;
}

/* Force centering for world name and launch headers */
body:has(.google_header) th.card_worldName,
body:has(.google_header) th[colspan="2"].card_worldName {
	text-align: center !important;
	font-weight: bold !important;
}

body:has(.google_header) th.card_worldLaunch,
body:has(.google_header) th[colspan="2"].card_worldLaunch {
	text-align: center !important;
}

body:has(.google_header) a.card_worldLaunch {
	text-align: center !important;
	display: block !important;
	border: none !important;
	outline: none !important;
	box-shadow: none !important;
}

/* Remove border around offline label in dimmed cards */
body:has(.google_header) .card_dimmed.card_worldLaunch,
body:has(.google_header) a.card_dimmed.card_worldLaunch,
body:has(.google_header) th.card_dimmed.card_worldLaunch {
	border: none !important;
	outline: none !important;
	box-shadow: none !important;
	background: transparent !important;
}

/* Ensure the inner table in catbox takes full width for centering */
body:has(.google_header) .catbox > table {
	width: 100% !important;
}

/* Header - google avatar */
body:has(.google_header) .google_header {
	text-align: left !important;
	padding-left: 8px !important;
	padding-top: 8px !important;
}

body:has(.google_header) .google_header img {
	width: 75px !important;
	height: 75px !important;
	border-radius: 50% !important;
	margin-top: 15px !important;
}

/* Header - download button */
body:has(.google_header) .client_download_button {
	text-align: right !important;
	padding-top: 0 !important;
	padding-right: 0 !important;
	font-family: var(--font-mono-ui) !important;
	font-size: 100% !important;
}

/* Header right section (download + sign out) */
body:has(.google_header) .header_right_section {
	width: 100% !important;
	text-align: right !important;
	padding-top: 10px !important;
	vertical-align: top !important;
	position: relative !important;
}

body:has(.google_header) .header_right_inner {
	display: flex !important;
	justify-content: flex-end !important;
	align-items: center !important;
	gap: 12px !important;
	position: absolute !important;
	right: -15px !important;
	top: 15px !important;
}

.signout-icon {
	color: #94a3b8 !important;
	transition: color 0.2s ease;
	pointer-events: auto !important;
	cursor: pointer !important;
	display: inline-flex !important;
}

.signout-icon:hover {
	color: orchid !important;
}

body:has(.google_header) .signout-icon {
	color: #94a3b8 !important;
}

body:has(.google_header) .signout-icon:hover {
	color: orchid !important;
}

/* Header - welcome name */
body:has(.google_header) .name_header {
	color: #00CED1 !important;
	text-align: left !important;
	padding-left: 8px !important;
	padding-top: 8px !important;
	font-weight: bold !important;
	font-size: 16px !important;
	font-family: var(--font-mono-ui) !important;
}

/* Card grid container */
.outer {
	height: 100% !important;
	width: 100% !important;
	max-height: 100% !important;
	margin-top: 50px !important;
	display: flex !important;
	justify-content: center !important;
}

.inner {
	width: auto !important;
	max-width: 1500px !important;
	padding: 50px !important;
	height: auto !important;
	max-height: 100% !important;
	overflow: visible !important;
	margin-left: auto !important;
	margin-right: auto !important;
	display: flex !important;
	justify-content: center !important;
	align-content: center !important;
}

.wrapper {
	display: flex !important;
	flex-wrap: wrap !important;
	grid-template-columns: none !important;
	gap: 30px !important;
	background-color: #131729 !important;
	padding: 50px !important;
	overflow: visible !important;
	justify-content: center !important;
	align-content: center !important;
}

/* Card dimmed state - grey dashed border for offline */
.card_dimmed {
	cursor: not-allowed !important;
	text-decoration: none !important;
	pointer-events: none !important;
	color: #A0A0A0 !important;
	opacity: 0.75 !important;
}

/* Offline card box - dashed grey border */
.catbox.card_dimmed,
div.card_dimmed.catbox {
	outline: 2px dashed #A0A0A0 !important;
	border: none !important;
	width: 400px !important;
	min-width: 400px !important;
	max-width: 400px !important;
}

/* Offline card - grey world name and text */
.card_dimmed.card_worldName,
th.card_dimmed.card_worldName {
	color: #A0A0A0 !important;
}

.card_dimmed.card_worldLaunch,
th.card_dimmed.card_worldLaunch,
a.card_dimmed.card_worldLaunch,
a.card_dimmed.card_worldLaunch:link,
a.card_dimmed.card_worldLaunch:visited,
a.card_dimmed.card_worldLaunch:hover {
	color: #A0A0A0 !important;
}

/* Collapsed borders on EVERY card, online or offline.
   This used to be part of the offline-card rule below, so a live card kept the browser default
   border-collapse: separate with its 2px border-spacing -- every row 2px further apart and the
   whole card ~14px taller than the identical offline card sitting next to it. Row heights were
   the same on both; it was the spacing between them that differed, which is why it looked like
   the online cards had gaps. */
body:has(.google_header) .catbox table {
	border-spacing: 0 !important;
	border-collapse: collapse !important;
}

/* Offline card - no borders on inner tables */
.catbox.card_dimmed table,
div.card_dimmed.catbox table,
body:has(.google_header) .catbox.card_dimmed table {
	outline: none !important;
	border: none !important;
	border-spacing: 0 !important;
	border-collapse: collapse !important;
}

.catbox.card_dimmed th,
.catbox.card_dimmed td,
div.card_dimmed.catbox th,
div.card_dimmed.catbox td,
body:has(.google_header) .catbox.card_dimmed th,
body:has(.google_header) .catbox.card_dimmed td {
	outline: none !important;
	border: none !important;
	background: transparent !important;
}

/* All inner tables in cards - no visible borders */
.catbox table {
	outline: none !important;
	border: none !important;
}

/* Card box */
.catbox {
	background-color: #131729 !important;
	border-radius: 6px !important;
	padding: 12px !important;
	/* Was 50px, and 50px of padding was the only thing keeping the boss trophies on the card:
	   the trophy table is a SIBLING after a height:100% table, so it was being laid out past
	   the bottom of the card and the padding merely hid that. Same fault the crossplay hint
	   had. Stacking the card as a flex column (below) makes the trophy row a real part of the
	   card's height, so the padding can go back to being padding. */
	padding-bottom: 12px !important;
	font-size: 125% !important;
	outline: 2px solid #34e2e2 !important;
	border: none !important;
	width: 400px !important;
	min-width: 400px !important;
	max-width: 400px !important;
}

/* Card world name - centered and bold */
.card_worldName {
	color: #32afff !important;
	font-size: 20px !important;
	outline: none !important;
	text-align: center !important;
	font-weight: bold !important;
}

/* Card launch button - centered and bold */
.card_worldLaunch {
	font-size: 15px !important;
	outline: none !important;
	text-align: center !important;
	font-weight: bold !important;
}

th.card_worldName,
th.card_worldLaunch {
	text-align: center !important;
}

a.card_worldLaunch:link,
a.card_worldLaunch:visited {
	color: orchid !important;
}

a.card_worldLaunch:hover {
	color: palegreen !important;
}

/* Card info rows.
   14px, not 12: 12px is under every readability guideline for body text (16px is the web
   baseline, and secondary/tabular text is not meant to go below ~13-14px). JetBrains Mono has a
   noticeably taller x-height than the Courier-family face this used to fall back to, so it
   reads larger again on top of that.
   14 also lands the rhythm on whole pixels -- 14 x 1.5 = 21 exactly. At 13px the 19.5px rows
   rounded to 19 or 20 depending on where the card happened to start, so two identical cards
   measured differently. */
.card_worldInfo {
	color: #d3d7cf !important;
	font-size: 14px !important;
	outline: none !important;
	font-weight: normal !important;
	text-align: left !important;
}

/* Hierarchy without changing a single size: the LABEL is the repeated, skippable half of the
   row and the VALUE is what the player came for. They were the same colour, so every row had
   two equally loud halves. Guidance for dense tables is to reserve the brighter colour for the
   data and mute the supporting label. */
.catbox td.card_worldInfo:first-child {
	color: #8b93a7 !important;
}

/* Trophy icons */
.trophy_icon {
	width: 55px !important;
	margin-left: auto !important;
	margin-right: auto !important;
	text-align: center !important;
	vertical-align: middle !important;
	-webkit-filter: brightness(120%) !important;
	filter: brightness(120%) !important;
}

/* Add top spacing to trophy table when world is offline */
.catbox.card_dimmed > table:last-child {
	margin-top: 5px !important;
}

.trophy_icon img {
	width: 36px !important;
	height: 36px !important;
}

.trophy_dimmed {
	-webkit-filter: grayscale(100%) !important;
	filter: grayscale(100%) brightness(100%) opacity(25%) !important;
}

/* Popover styling for download button */
body:has(.google_header) .popover {
	min-width: 100px !important;
	max-width: 100% !important;
	background: #131729 !important;
	border: 2px solid #34e2e2 !important;
	border-radius: 6px !important;
	font-family: var(--font-mono-ui) !important;
	overflow: visible !important;
}

body:has(.google_header) .popover-title,
body:has(.google_header) .popover-header,
.popover-header,
.popover-title {
	background: #131729 !important;
	border-color: #34e2e2 !important;
	color: palegreen !important;
	padding: 8px 10px !important;
	font-size: 90% !important;
	text-transform: none !important;
	text-align: center !important;
}

body:has(.google_header) .popover-header .client_download_tooltip,
.popover-header .client_download_tooltip,
.client_download_tooltip,
.popover-header b,
.popover-title b {
	text-transform: none !important;
}

body:has(.google_header) .popover-body {
	background: #131729 !important;
	color: #DEDEDE !important;
	padding: 5px 10px !important;
	border-radius: 0 0 4px 4px !important;
}

/* Fix bullet points being cut off in mod list popover */
body:has(.google_header) .popover-body li {
	list-style-position: inside !important;
	margin-left: 0 !important;
	padding-left: 0 !important;
}

body:has(.google_header) .popover .arrow::after,
body:has(.google_header) .bs-popover-bottom .arrow::after,
body:has(.google_header) .popover-arrow::after,
body:has(.google_header) .bs-popover-bottom > .popover-arrow::after,
.popover-arrow::after {
	border-bottom-color: white !important;
}

body:has(.google_header) .popover .arrow::before,
body:has(.google_header) .bs-popover-bottom .arrow::before,
body:has(.google_header) .popover-arrow::before,
body:has(.google_header) .bs-popover-bottom > .popover-arrow::before,
.popover-arrow::before {
	border-bottom-color: white !important;
}

/* Download button icons */
.client_download_os_icon {
	text-align: center !important;
	display: block !important;
	width: 100% !important;
}

.client_download_os_icon img {
	margin-top: 5px !important;
	width: 32px !important;
	height: 32px !important;
	filter: brightness(75%) !important;
}

.versionLabel {
	margin-bottom: 0px !important;
	padding-bottom: 0px !important;
}

.client_download_os_icon:hover img {
	filter: brightness(110%) !important;
}

.client_download_tooltip {
	font-family: var(--font-mono-ui) !important;
	font-weight: normal !important;
	font-size: 100% !important;
	background-color: #131729 !important;
	color: palegreen !important;
	border-color: #34e2e2 !important;
	text-align: right !important;
}

.versionLabel {
	display: block !important;
	font-size: 0.83em !important;
	margin-top: 2px !important;
	color: orchid !important;
}

.versionLabel:hover {
	color: palegreen !important;
}

p.client_download_tooltip_otherbuilds {
	font-family: var(--font-mono-ui) !important;
	font-weight: normal !important;
	font-size: 65% !important;
	padding-top: 0px !important;
	display: flex !important;
	justify-content: center !important;
	align-items: center !important;
	margin: 0 auto !important;
}

p.client_download_tooltip_otherbuilds a {
	color: orchid !important;
	text-decoration: none !important;
}

p.client_download_tooltip_otherbuilds:hover a {
	color: palegreen !important;
}

/* DataTables dark theme */
.dataTables_wrapper {
	color: var(--text-primary);
}

.dataTables_info {
	color: var(--text-secondary) !important;
	padding: 0.75rem 0;
}

.dataTables_length {
	color: var(--text-primary) !important;
	margin-bottom: 1rem;
}

.dataTables_length select {
	color: var(--text-primary) !important;
	background-color: var(--bg-secondary) !important;
	border: 1px solid var(--border-color);
	border-radius: 0.375rem;
	padding: 0.375rem 0.75rem;
	margin: 0 0.5rem;
}

.dataTables_filter {
	color: var(--text-primary) !important;
	margin-bottom: 1rem;
}

.dataTables_filter input {
	color: var(--text-primary) !important;
	background-color: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.375rem;
	padding: 0.375rem 0.75rem;
	margin-left: 0.5rem;
}

.dataTables_filter input:focus {
	border-color: var(--accent-primary);
	outline: none;
	box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15);
}

.dataTables_paginate {
	margin-top: 1rem !important;
	padding-top: 0.75rem;
}

.dataTables_wrapper .dataTables_paginate .paginate_button {
	color: var(--accent-secondary) !important;
	background: transparent !important;
	border: none !important;
	padding: 0.375rem 0.75rem;
	margin: 0 0.125rem;
	border-radius: 0.375rem;
	transition: all 0.2s ease;
}

.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
	color: var(--success) !important;
	background: var(--bg-tertiary) !important;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.current {
	color: var(--text-primary) !important;
	background: var(--bg-tertiary) !important;
	border: 1px solid var(--accent-primary) !important;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
	color: var(--text-primary) !important;
	background: var(--bg-tertiary) !important;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.disabled {
	color: var(--text-muted) !important;
	cursor: not-allowed;
}

/* DataTables rows */
table.dataTable tbody tr {
	background-color: var(--bg-primary);
	transition: background-color 0.15s ease;
}

table.dataTable tbody tr:hover {
	background-color: var(--bg-secondary);
}

table.dataTable tbody tr.myodd {
	background-color: var(--bg-secondary);
}

table.dataTable tbody tr.myeven {
	background-color: var(--bg-primary);
}

table.dataTable thead th {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	font-weight: 600;
	border-bottom: 2px solid var(--accent-primary);
	padding: 0.875rem 0.75rem;
}

table.dataTable thead th.sorting,
table.dataTable thead th.sorting_asc,
table.dataTable thead th.sorting_desc {
	cursor: pointer;
}

/* Fix white table background in mod selection tables */
table.table {
	background-color: var(--bg-primary);
	color: var(--text-primary);
}

table.table thead th {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	border-color: var(--border-color);
}

table.table tbody td {
	background-color: var(--bg-primary);
	color: var(--text-primary);
	border-color: var(--border-light);
}

table.table tbody tr:hover {
	background-color: var(--bg-secondary);
}

table.table-hover tbody tr:hover {
	background-color: var(--bg-secondary);
}

/* Bootstrap override buttons */
.btn-outline-download {
	color: orchid !important;
	border: 1px solid #34e2e2 !important;
	border-bottom: 3px solid #1a8a8a !important;
	background: linear-gradient(to bottom, #1e2d4a, #162038) !important;
	font-family: var(--font-mono-ui) !important;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(52, 226, 226, 0.1) !important;
	transition: all 0.15s ease !important;
}

.btn-outline-download:hover {
	color: palegreen !important;
	background: linear-gradient(to bottom, #243556, #1e2d4a) !important;
	border-color: #34e2e2 !important;
	border-bottom-color: #1a8a8a !important;
	box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(52, 226, 226, 0.15) !important;
}

.btn-outline-download:active,
.btn-outline-download:not(:disabled):not(.disabled):active,
.btn-outline-download:not(:disabled):not(.disabled).active,
.show > .btn-outline-download.dropdown-toggle {
	color: orchid !important;
	transform: translateY(2px) !important;
	border-bottom-width: 1px !important;
	background: linear-gradient(to bottom, #162038, #1e2d4a) !important;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3) !important;
	border-color: #34e2e2 !important;
}

.btn-sm, .btn-group-sm > .btn {
	padding: 0.25rem 0.5rem;
	font-size: 0.875rem;
	line-height: 1.25;
	border-radius: 0.25rem;
	background-color: var(--bg-secondary);
}

/* macOS Install Modal — scoped overrides for Bootstrap modal on public UI */
#macInstallModal.modal {
	display: none;
	position: fixed;
	z-index: 9999;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background-color: rgba(0, 0, 0, 0.6);
	backdrop-filter: blur(6px);
}

#macInstallModal .modal-dialog {
	max-width: 648px;
	margin: auto;
}

#macInstallModal .modal-content.mac-install-modal {
	background: linear-gradient(to bottom, #1e2d4a, #131729) !important;
	border: 1px solid #34e2e2;
	border-radius: 12px;
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6), 0 0 20px rgba(52, 226, 226, 0.08);
	font-family: var(--font-mono-ui);
	padding: 0;
	margin: 0;
	width: 100%;
	max-width: 648px;
}

.mac-install-header {
	border-bottom: 1px solid rgba(52, 226, 226, 0.2);
	padding: 1rem 1.25rem;
	display: flex;
	align-items: center;
}

.mac-install-header .modal-title {
	color: palegreen;
	font-size: 0.95rem;
	margin: 0;
}

.mac-install-body {
	padding: 1.25rem;
	color: #DEDEDE;
	font-size: 0.85rem;
	line-height: 1.6;
}

.mac-install-body p {
	margin-bottom: 0.75rem;
}

.mac-install-command-wrap {
	display: flex;
	align-items: center;
	background: rgba(0, 0, 0, 0.35);
	border: 1px solid rgba(52, 226, 226, 0.25);
	border-radius: 8px;
	padding: 0.75rem 1rem;
	margin-bottom: 1rem;
	gap: 0.75rem;
}

.mac-install-command {
	color: #34e2e2;
	font-size: 0.78rem;
	word-break: break-all;
	flex: 1;
	user-select: all;
}

.mac-install-copy-btn {
	color: #DEDEDE;
	background: none;
	border: 1px solid rgba(52, 226, 226, 0.3);
	border-radius: 6px;
	padding: 4px 6px;
	flex-shrink: 0;
	transition: all 0.15s ease;
}

.mac-install-copy-btn:hover {
	color: palegreen;
	border-color: #34e2e2;
	background: rgba(52, 226, 226, 0.1);
}

.mac-install-note {
	color: #9a9ab0;
	font-size: 0.8rem;
	margin-bottom: 0 !important;
}

.mac-install-footer {
	border-top: 1px solid rgba(52, 226, 226, 0.2);
	padding: 0.75rem 1.25rem;
	display: flex;
	justify-content: space-between;
	align-items: center;
}

.mac-install-ref-link {
	color: orchid;
	font-size: 0.78rem;
	text-decoration: none;
	transition: color 0.15s ease;
}

.mac-install-ref-link:hover {
	color: palegreen;
}

/* Bootstrap 5 Popover overrides */
.popover {
	min-width: 100px;
	max-width: 100%;
	background: var(--bg-secondary);
	border-color: var(--accent-primary);
	border-radius: 0.5rem;
}

.popover-header {
	background: var(--bg-tertiary);
	border-color: var(--accent-primary);
	color: var(--success);
	font-weight: 600;
}

.popover-body {
	color: var(--text-primary);
}

.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,
.bs-popover-top>.popover-arrow::after {
	border-top-color: var(--bg-secondary);
}

.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,
.bs-popover-bottom>.popover-arrow::after {
	border-bottom-color: var(--bg-secondary);
}

/* Overlay and loading states */
.overlay {
	position: fixed;
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	background-color: var(--bg-primary);
	opacity: 0.95;
	z-index: 1000;
}

.blur {
	filter: blur(5px);
	width: 100%;
	height: 100%;
	position: absolute;
	z-index: -10;
}

.unblur {
	filter: none;
}

.noscroll {
	margin: 0;
	height: 100%;
	overflow: hidden;
}

.scroll {
	margin: 0;
	height: 100%;
	overflow: visible;
}

.loading {
	width: 100%;
	height: 100%;
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	background-color: rgba(15, 23, 42, 0.95);
	z-index: 2000;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* Wave dots spinner (default) */
.loading-dots {
	display: flex;
	gap: 8px;
	align-items: center;
}

.loading-dots span {
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--accent-primary);
	animation: loading-wave 1.4s ease-in-out infinite;
}

.loading-dots span:nth-child(2) { animation-delay: 0.12s; }
.loading-dots span:nth-child(3) { animation-delay: 0.24s; }
.loading-dots span:nth-child(4) { animation-delay: 0.36s; }
.loading-dots span:nth-child(5) { animation-delay: 0.48s; }

@keyframes loading-wave {
	0%, 100% { transform: translateY(0) scale(1); opacity: 0.3; }
	50% { transform: translateY(-20px) scale(1.4); opacity: 1; }
}

/* Eye of Sauron spinner (long load) */
.loading-eye {
	display: none;
	width: 100px;
	height: 44px;
	border-radius: 50%;
	background: radial-gradient(ellipse at center, #ff6a00 0%, #cc3300 30%, #661100 60%, #220500 100%);
	position: relative;
	overflow: hidden;
	box-shadow:
		0 0 40px 10px rgba(255, 80, 0, 0.6),
		0 0 80px 20px rgba(204, 30, 0, 0.3),
		inset 0 0 15px rgba(255, 106, 0, 0.8);
	animation: sauron-pulse 2s ease-in-out infinite;
}

.loading-eye::before {
	content: '';
	position: absolute;
	top: 50%;
	left: 50%;
	width: 8px;
	height: 34px;
	border-radius: 50%;
	background: #000;
	transform: translate(-50%, -50%);
	box-shadow:
		0 0 6px 2px rgba(255, 80, 0, 0.9),
		inset 0 0 4px rgba(255, 106, 0, 0.5);
	animation: sauron-look 1.2s ease-in-out infinite;
}

.loading-eye::after {
	content: '';
	position: absolute;
	top: -2px;
	left: -2px;
	right: -2px;
	bottom: -2px;
	border-radius: 50%;
	border: 2px solid transparent;
	border-top-color: rgba(255, 150, 0, 0.4);
	border-bottom-color: rgba(255, 150, 0, 0.4);
	animation: sauron-flicker 0.8s ease-in-out infinite alternate;
}

@keyframes sauron-look {
	0% { left: 50%; }
	8% { left: 68%; }
	18% { left: 35%; }
	30% { left: 62%; }
	42% { left: 40%; }
	55% { left: 66%; }
	68% { left: 38%; }
	80% { left: 58%; }
	92% { left: 44%; }
	100% { left: 50%; }
}

@keyframes sauron-pulse {
	0%, 100% {
		box-shadow:
			0 0 40px 10px rgba(255, 80, 0, 0.6),
			0 0 80px 20px rgba(204, 30, 0, 0.3),
			inset 0 0 15px rgba(255, 106, 0, 0.8);
	}
	50% {
		box-shadow:
			0 0 55px 15px rgba(255, 80, 0, 0.8),
			0 0 100px 30px rgba(204, 30, 0, 0.5),
			inset 0 0 20px rgba(255, 140, 0, 1);
	}
}

@keyframes sauron-flicker {
	0% { opacity: 0.3; transform: rotate(0deg); }
	100% { opacity: 0.7; transform: rotate(6deg); }
}

/* Toggle switch */
.switch {
	position: relative;
	display: inline-block;
	width: 32px;
	height: 18px;
	vertical-align: middle;
}

.switch input {
	opacity: 0;
	width: 0;
	height: 0;
}

.slider {
	position: absolute;
	cursor: pointer;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background-color: var(--bg-tertiary);
	transition: 0.3s;
}

.slider:before {
	position: absolute;
	content: "";
	height: 12px;
	width: 12px;
	left: 3px;
	bottom: 3px;
	background-color: var(--text-secondary);
	transition: 0.3s;
}

/* The pill shrank; the thing you have to hit did not.
   A 32x18 switch is under the 24px minimum for a pointer target, so an invisible box is
   extended past the pill to keep the clickable area 36x24. It sits inside the <label>, so it
   toggles like the pill does -- this buys back the hit area without putting it back on screen. */
.slider::after {
	content: "";
	position: absolute;
	top: -3px;
	bottom: -3px;
	left: -2px;
	right: -2px;
}

input:checked + .slider {
	background-color: var(--success);
}

input:focus + .slider {
	box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.25);
}

input:checked + .slider:before {
	transform: translateX(14px);
	background-color: white;
}

.slider.round {
	border-radius: 20px;
}

.slider.round:before {
	border-radius: 50%;
}

/* Modal styling */
.modal {
	display: none;
	position: fixed;
	z-index: 1050;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: auto;
	background-color: rgba(15, 23, 42, 0.8);
	backdrop-filter: blur(4px);
}

.modal-content {
	background-color: var(--bg-secondary);
	margin: 10% auto;
	padding: 1.5rem;
	border: 1px solid var(--accent-primary);
	border-radius: 0.5rem;
	width: 90%;
	max-width: 600px;
	box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}

.modal-content h2 {
	color: var(--success);
	margin-bottom: 1rem;
}

.close {
	color: var(--text-secondary);
	float: right;
	font-size: 1.75rem;
	font-weight: bold;
	line-height: 1;
	cursor: pointer;
	transition: color 0.2s ease;
}

.close:hover,
.close:focus {
	color: var(--danger);
}

/* Responsive adjustments */
@media (max-width: 1200px) {
	.center {
		width: 100% !important;
		overflow-x: auto;
	}
}

@media (max-width: 992px) {
	body {
		padding: 0.75rem;
	}

	.inner {
		padding: 1rem;
	}

	table#worlds {
		font-size: 0.875rem;
	}
}

@media (max-width: 768px) {
	body {
		padding: 0.5rem;
	}

	.sm-bttn {
		padding: 0.375rem 0.75rem;
		font-size: 0.8125rem;
	}

	table th, table td {
		padding: 0.5rem;
	}

	.wrapper {
		grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
		gap: 1rem;
	}

	.modal-content {
		margin: 5% auto;
		padding: 1rem;
		width: 95%;
	}
}

@media (max-width: 576px) {
	.dataTables_length,
	.dataTables_filter {
		float: none;
		text-align: center;
		margin-bottom: 0.75rem;
	}

	.dataTables_filter input {
		width: 100%;
		margin-left: 0;
		margin-top: 0.5rem;
	}

	table.dataTable thead th {
		padding: 0.625rem 0.5rem;
		font-size: 0.8125rem;
	}
}

/* Print styles */
@media print {
	body {
		background: white;
		color: black;
	}

	.sm-bttn,
	.dataTables_filter,
	.dataTables_length,
	.dataTables_paginate {
		display: none !important;
	}
}

/* ============================================
   ADMIN DASHBOARD - Sidebar Layout
   ============================================ */

.admin-layout {
	display: flex;
	min-height: 100vh;
	margin: -1rem; /* Counter body padding */
}

/* Sidebar */
.admin-sidebar {
	width: 240px;
	background: linear-gradient(180deg, #0c1222 0%, #0f172a 100%);
	border-right: 1px solid var(--border-color);
	display: flex;
	flex-direction: column;
	position: fixed;
	top: 0;
	left: 0;
	height: 100vh;
	z-index: 100;
	transition: transform 0.3s ease;
}

.sidebar-header {
	padding: 1.25rem 1rem;
	border-bottom: 1px solid var(--border-color);
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.sidebar-logo {
	width: 32px;
	height: 32px;
	background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
	border-radius: 8px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	font-size: 1rem;
	color: var(--bg-primary);
}

.sidebar-title {
	font-size: 1.125rem;
	font-weight: 600;
	color: var(--text-primary);
}

.sidebar-version {
	font-size: 0.75rem;
	color: var(--text-muted);
	margin-left: auto;
}

.sidebar-nav {
	flex: 1;
	padding: 1rem 0;
	overflow-y: auto;
}

.nav-section {
	margin-bottom: 1.5rem;
}

.nav-section-title {
	font-size: 0.6875rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--text-muted);
	padding: 0 1rem;
	margin-bottom: 0.5rem;
}

.nav-item {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.625rem 1rem;
	color: var(--text-secondary);
	text-decoration: none;
	transition: all 0.2s ease;
	border-left: 3px solid transparent;
	cursor: pointer;
}

.nav-item:hover {
	background: var(--bg-secondary);
	color: var(--text-primary);
}

.nav-item.active {
	background: rgba(34, 211, 238, 0.1);
	color: var(--accent-primary);
	border-left-color: var(--accent-primary);
}

.nav-item svg, .nav-item .nav-icon {
	width: 18px;
	height: 18px;
	flex-shrink: 0;
	opacity: 0.7;
}

.nav-item:hover svg, .nav-item:hover .nav-icon {
	opacity: 1;
}

.nav-submenu {
	display: none;
	padding-left: 2.5rem;
	background: rgba(0, 0, 0, 0.2);
}

.nav-submenu.show {
	display: block;
}

.nav-submenu .nav-item {
	padding: 0.5rem 1rem;
	font-size: 0.875rem;
	border-left: none;
}

.nav-chevron {
	margin-left: auto;
	transition: transform 0.2s ease;
}

.nav-item.expanded .nav-chevron {
	transform: rotate(90deg);
}

/* Main Content Area */
.admin-main {
	flex: 1;
	margin-left: 240px;
	padding: 1.5rem;
	min-height: 100vh;
	background: var(--bg-primary);
}

.admin-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 1.5rem;
	padding-bottom: 1rem;
	border-bottom: 1px solid var(--border-color);
}

.admin-header h1 {
	font-size: 1.5rem;
	font-weight: 600;
	color: var(--text-primary);
	margin: 0;
}

.header-actions {
	display: flex;
	gap: 0.75rem;
	align-items: center;
}

/* Sidebar collapse button in admin header */
.sidebar-collapse-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border: none;
	background: transparent;
	color: var(--text-secondary);
	cursor: pointer;
	border-radius: 6px;
	transition: all 0.2s ease;
	flex-shrink: 0;
	padding: 0;
	margin-right: 0.75rem;
}

.sidebar-collapse-btn:hover {
	background: var(--bg-secondary);
	color: var(--text-primary);
}

.sidebar-collapse-btn .collapse-icon {
	transition: transform 0.3s ease;
}

.admin-layout.sidebar-collapsed .sidebar-collapse-btn .collapse-icon {
	transform: rotate(180deg);
}

/* Sidebar collapsed state (desktop) */
.admin-layout.sidebar-collapsed .admin-sidebar {
	transform: translateX(-100%);
}

.admin-layout.sidebar-collapsed .admin-main {
	margin-left: 0 !important;
}

/* Smooth transition for main content */
.admin-main {
	transition: margin-left 0.3s ease;
}

/* Admin footer with social links - sits at bottom of sidebar */
.admin-footer {
	display: flex;
	gap: 0.5rem;
	padding: 0.75rem 1rem;
	border-top: 1px solid var(--border-color);
	align-items: center;
	flex-shrink: 0;
}

.social-link {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.5rem;
	color: var(--text-secondary);
	transition: all 0.2s ease;
	text-decoration: none;
}

.social-link:hover {
	background: var(--bg-tertiary);
	border-color: var(--accent-secondary);
	color: var(--accent-secondary);
	transform: translateY(-2px);
}

.footer-version {
	display: flex;
	align-items: center;
	margin-left: 4.5rem;
	padding-right: 0.5rem;
	color: var(--text-muted);
	font-size: 0.75rem;
	font-weight: 500;
	letter-spacing: 0.025em;
}

.header-time {
	font-size: 0.875rem;
	color: var(--text-secondary);
}

/* Stats Cards Row */
.stats-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap: 1rem;
	margin-bottom: 1.5rem;
}

.stat-card {
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	padding: 1rem;
	display: flex;
	align-items: flex-start;
	gap: 0.75rem;
	transition: border-color 0.2s ease;
}

.stat-card:hover {
	border-color: var(--accent-primary);
}

.stat-icon {
	width: 40px;
	height: 40px;
	border-radius: 0.5rem;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 1.125rem;
	flex-shrink: 0;
}

.stat-icon svg {
	width: 20px;
	height: 20px;
}

.stat-icon.worlds {
	background: rgba(34, 211, 238, 0.15);
	color: var(--accent-primary);
}

.stat-icon.memory {
	background: rgba(74, 222, 128, 0.15);
	color: var(--success);
}

.stat-icon.cpu {
	background: rgba(167, 139, 250, 0.15);
	color: var(--accent-secondary);
}

.stat-icon.disk {
	background: rgba(251, 191, 36, 0.15);
	color: var(--warning);
}

.stat-content {
	flex: 1;
	min-width: 0;
}

.stat-label {
	font-size: 0.75rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.025em;
	color: var(--text-muted);
	margin-bottom: 0.25rem;
}

.stat-value {
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--text-primary);
	line-height: 1.2;
}

.stat-subtext {
	font-size: 0.75rem;
	color: var(--text-secondary);
	margin-top: 0.25rem;
}

/* Stat cards with charts */
.stat-card-chart {
	flex-direction: column;
	padding-bottom: 0.5rem;
}

.stat-card-chart .stat-card-top {
	display: flex;
	align-items: flex-start;
	gap: 0.75rem;
	width: 100%;
}

.stat-chart-container {
	width: 100%;
	height: 40px;
	margin-top: 0.5rem;
	padding: 0 0.25rem;
}

.stat-chart-container canvas {
	width: 100% !important;
	height: 100% !important;
}

/* Dashboard Cards */
.dashboard-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: 1.5rem;
}

.dashboard-grid.two-col {
	grid-template-columns: repeat(2, 1fr);
}

.dashboard-grid.three-col {
	grid-template-columns: repeat(3, 1fr);
}

.dashboard-card {
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	overflow: hidden;
}

.card-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 1rem 1.25rem;
	border-bottom: 1px solid var(--border-color);
	background: rgba(0, 0, 0, 0.2);
}

.card-title {
	font-size: 0.9375rem;
	font-weight: 600;
	color: var(--text-primary);
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.card-title-icon {
	color: var(--accent-primary);
}

.card-body {
	padding: 1.25rem;
}

.card-body.no-padding {
	padding: 0;
}

/* Status Badge Styles */
/* Saved world options that Valheim has not seen yet.
   Amber rather than red: nothing is broken, the world is running exactly as it was started --
   it is just no longer running what the settings page says. Sits next to the world name so the
   Worlds table answers "why is this world not behaving like I configured it?" on sight. */
.restart-pending-badge {
	display: inline-flex;
	align-items: center;
	margin-left: 0.5rem;
	padding: 0.125rem 0.5rem;
	border-radius: 9999px;
	font-size: 0.6875rem;
	font-weight: 500;
	white-space: nowrap;
	background: rgba(251, 191, 36, 0.15);
	color: #fbbf24;
	cursor: help;
}

.status-badge {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	padding: 0.25rem 0.625rem;
	border-radius: 9999px;
	font-size: 0.75rem;
	font-weight: 500;
	text-transform: capitalize;
	background: rgba(100, 116, 139, 0.15);
	color: var(--text-secondary);
}

/* Running state - solid green background, pulsing dot */
.status-badge.running {
	background: rgba(74, 222, 128, 0.15);
	color: #4ade80;
	animation: none;
}

/* Stopped state - dark/muted */
.status-badge.stopped {
	background: rgba(100, 116, 139, 0.15);
	color: #64748b;
	animation: none;
}

/* Transitional states - orange pulse */
.status-badge.start,
.status-badge.stop,
.status-badge.starting,
.status-badge.stopping,
.status-badge.create,
.status-badge.update,
.status-badge.delete,
.status-badge.creating,
.status-badge.updating,
.status-badge.deleting,
.status-badge.backup {
	background: rgba(251, 191, 36, 0.2);
	color: #fbbf24;
	animation: status-pulse 1.2s ease-in-out infinite;
}

.status-dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: currentColor;
}

/* Running status dot also pulses - offset animation to handle AJAX refresh smoothly */
.status-badge.running .status-dot {
	background: #4ade80;
	animation: pulse 3.5s ease-in-out infinite;
	animation-delay: -1.75s; /* Start halfway through cycle to avoid all dots syncing on AJAX refresh */
	will-change: opacity, box-shadow;
}

/* Simple pulse animation for transitional states */
@keyframes status-pulse {
	0%, 100% {
		opacity: 1;
		box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.5);
	}
	50% {
		opacity: 0.7;
		box-shadow: 0 0 8px 2px rgba(251, 191, 36, 0.4);
	}
}

/* Worlds Table Modern Style */
.worlds-table {
	width: 100%;
	border-collapse: collapse;
	table-layout: fixed;
}

/* Fixed column widths for consistent alignment.
   Actions and Configure are sized to comfortably hold their button groups
   without wrapping at typical viewport widths (≥ ~1450px table width).
   Endpoint and Seed are trimmed to compensate. */
.worlds-table th:nth-child(1),
.worlds-table td:nth-child(1) { width: 7%; }  /* Status */
.worlds-table th:nth-child(2),
.worlds-table td:nth-child(2) { width: 15%; } /* World */
.worlds-table th:nth-child(3),
.worlds-table td:nth-child(3) { width: 18%; } /* Actions */
.worlds-table th:nth-child(4),
.worlds-table td:nth-child(4) { width: 40%; } /* Configure */
.worlds-table th:nth-child(5),
.worlds-table td:nth-child(5) { width: 20%; } /* Resources */

.worlds-table thead th {
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	font-size: 0.75rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.025em;
	padding: 0.875rem 1rem;
	text-align: left;
	border-bottom: 1px solid var(--border-color);
}

.worlds-table tbody tr {
	border-bottom: 1px solid var(--border-light);
	transition: background 0.15s ease;
}

.worlds-table tbody tr:hover {
	background: rgba(34, 211, 238, 0.03);
}

.worlds-table tbody tr:last-child {
	border-bottom: none;
}

.worlds-table tbody td {
	padding: 1rem;
	color: var(--text-primary);
	vertical-align: middle;
}

.world-name {
	font-weight: 600;
	color: var(--text-primary);
	word-break: break-word;
}

.world-endpoint {
	font-family: var(--font-mono);
	font-size: 0.8125rem;
	color: var(--text-secondary);
	background: var(--bg-primary);
	padding: 0.25rem 0.5rem;
	border-radius: 0.25rem;
	display: inline-block;
	max-width: 100%;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.world-seed {
	font-family: var(--font-mono);
	font-size: 0.8125rem;
	color: var(--accent-secondary);
	display: block;
	max-width: 100%;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Action Buttons */
.action-group {
	display: flex;
	gap: 0.5rem;
	flex-wrap: nowrap;
	align-items: center;
}

.action-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.375rem 0.75rem;
	font-size: 0.8125rem;
	font-weight: 500;
	border-radius: 0.375rem;
	border: 1px solid transparent;
	cursor: pointer;
	transition: all 0.15s ease;
	text-decoration: none;
	background: transparent;
	color: var(--text-secondary);
	white-space: nowrap;
	flex-shrink: 0;
}

/* Responsive overflow dropdown (⋯ menu) — shown by JS when buttons don't fit */
.action-overflow {
	position: relative;
	display: inline-flex;
	flex-shrink: 0;
}

.action-overflow-trigger {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.75rem;
	height: 1.75rem;
	padding: 0;
	font-size: 1.125rem;
	line-height: 1;
	border-radius: 0.375rem;
	border: 1px solid transparent;
	cursor: pointer;
	background: transparent;
	color: var(--text-secondary);
	transition: all 0.15s ease;
}

.action-overflow-trigger:hover {
	background: var(--bg-tertiary);
	color: var(--text-primary);
}

.action-overflow-menu {
	display: none;
	position: absolute;
	right: 0;
	top: calc(100% + 0.25rem);
	z-index: 1000;
	min-width: 8rem;
	background: var(--bg-secondary);
	border: 1px solid var(--border-light);
	border-radius: 0.5rem;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
	padding: 0.25rem;
	flex-direction: column;
	gap: 0.125rem;
}

.action-overflow-menu.show {
	display: flex;
}

.action-overflow-menu .action-btn {
	display: flex;
	justify-content: flex-start;
	width: 100%;
	border-radius: 0.25rem;
	padding: 0.5rem 0.75rem;
}

.action-overflow-menu .action-btn.disabled {
	display: none;
}

.action-btn:hover {
	background: var(--bg-tertiary);
	color: var(--text-primary);
}

.action-btn.primary {
	background: rgba(34, 211, 238, 0.1);
	color: var(--accent-primary);
	border-color: rgba(34, 211, 238, 0.3);
}

.action-btn.primary:hover {
	background: rgba(34, 211, 238, 0.2);
}

.action-btn.success {
	background: rgba(74, 222, 128, 0.1);
	color: var(--success);
	border-color: rgba(74, 222, 128, 0.3);
}

.action-btn.success:hover {
	background: rgba(74, 222, 128, 0.2);
}

.action-btn.danger {
	background: rgba(248, 113, 113, 0.1);
	color: var(--danger);
	border-color: rgba(248, 113, 113, 0.3);
}

.action-btn.danger:hover {
	background: rgba(248, 113, 113, 0.2);
}

.action-btn.disabled, .action-btn:disabled {
	opacity: 0.4;
	cursor: not-allowed;
	pointer-events: none;
}

/* Hide disabled buttons in worlds table — only show actionable buttons */
.worlds-table .action-btn.disabled {
	display: none;
}

/* Mods Modal */
.mods-modal-overlay {
	display: none;
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: rgba(0, 0, 0, 0.7);
	backdrop-filter: blur(4px);
	z-index: 1000;
	align-items: center;
	justify-content: center;
}

.mods-modal-overlay.show {
	display: flex;
}

.mods-modal {
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	width: 90%;
	max-width: 900px;
	max-height: 80vh;
	display: flex;
	flex-direction: column;
	box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
	overflow: hidden;
}

.mods-modal-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 1rem 1.25rem;
	border-bottom: 1px solid var(--border-color);
}

.mods-modal-title {
	font-size: 1.125rem;
	font-weight: 600;
	color: var(--text-primary);
}

.mods-modal-close {
	background: none;
	border: none;
	color: var(--text-secondary);
	font-size: 1.5rem;
	cursor: pointer;
	padding: 0.25rem;
	line-height: 1;
	transition: color 0.15s ease;
}

.mods-modal-close:hover {
	color: var(--danger);
}

.mods-modal-body {
	flex: 1;
	overflow-y: auto;
	padding: 1.25rem;
}

.mods-list {
	list-style: none;
	padding: 0;
	margin: 0;
}

.mods-list li {
	padding: 0.625rem 0;
	border-bottom: 1px solid var(--border-light);
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.mods-list li:last-child {
	border-bottom: none;
}

.mods-list a {
	color: var(--accent-secondary);
	text-decoration: none;
	transition: color 0.15s ease;
}

.mods-list a:hover {
	color: var(--success);
}

.mods-count-badge {
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	font-size: 0.75rem;
	padding: 0.125rem 0.375rem;
	border-radius: 9999px;
	margin-left: 0.25rem;
	display: inline-block;
	min-width: 1.5rem;
	text-align: center;
	box-sizing: border-box;
}

/* Fixed width for View button so mod count doesn't shift alignment */
.action-btn[data-action="view-mods"] {
	min-width: 5.75rem;
	justify-content: center;
}

/* Quick Actions Panel */
.quick-actions {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	gap: 0.75rem;
}

.quick-action-btn {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
	padding: 1rem;
	background: var(--bg-primary);
	border: 1px solid var(--border-color);
	border-radius: 0.5rem;
	color: var(--text-secondary);
	text-decoration: none;
	transition: all 0.2s ease;
	cursor: pointer;
	font-size: 0.8125rem;
	font-weight: 500;
}

.quick-action-btn:hover {
	border-color: var(--accent-primary);
	color: var(--accent-primary);
	background: rgba(34, 211, 238, 0.05);
}

.quick-action-btn svg {
	width: 24px;
	height: 24px;
	opacity: 0.7;
}

.quick-action-btn:hover svg {
	opacity: 1;
}

/* Status Info List */
.status-info-list {
	list-style: none;
	padding: 0;
	margin: 0;
}

.status-info-item {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 0.75rem 0;
	border-bottom: 1px solid var(--border-light);
}

.status-info-item:last-child {
	border-bottom: none;
}

.status-info-label {
	color: var(--text-secondary);
	font-size: 0.875rem;
}

.status-info-value {
	color: var(--text-primary);
	font-size: 0.875rem;
	text-align: right;
}

.status-info-value .status-ok {
	color: var(--success);
}

.status-info-value .status-pending {
	color: var(--warning);
}

/* Live Update Indicator */
.live-indicator {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	font-size: 0.75rem;
	color: var(--success);
}

.live-indicator-dot {
	width: 8px;
	height: 8px;
	background: var(--success);
	border-radius: 50%;
	animation: pulse 3.5s ease-in-out infinite;
}

@keyframes pulse {
	0% {
		opacity: 1;
		box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7);
	}
	50% {
		opacity: 0.4;
		box-shadow: 0 0 12px 4px rgba(74, 222, 128, 0.4);
	}
	100% {
		opacity: 1;
		box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7);
	}
}

/* Mobile Sidebar Toggle */
.sidebar-toggle {
	display: none;
	position: fixed;
	top: 1rem;
	left: 1rem;
	z-index: 101;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.5rem;
	padding: 0.5rem;
	cursor: pointer;
	color: var(--text-primary);
}

/* Responsive */
@media (max-width: 1280px) {
	.dashboard-grid.two-col,
	.dashboard-grid.three-col {
		grid-template-columns: 1fr;
	}

	/* Allow table to scroll horizontally instead of squishing */
	.worlds-table {
		table-layout: auto;
		min-width: 1000px;
	}

	.worlds-scroll-wrapper,
	.offline-worlds-scroll-wrapper {
		overflow-x: auto;
	}

	/* Larger touch targets for buttons */
	.action-btn {
		padding: 0.5rem 0.875rem;
		font-size: 0.875rem;
	}
}

@media (max-width: 768px) {
	.admin-sidebar {
		transform: translateX(-100%);
	}

	.admin-sidebar.show {
		transform: translateX(0);
	}

	.admin-main {
		margin-left: 0;
		padding: 1rem;
	}

	.sidebar-toggle {
		display: block;
	}

	.sidebar-collapse-btn {
		display: none;
	}

	/* Header wraps on narrow screens */
	.admin-header {
		flex-wrap: wrap;
		gap: 0.75rem;
	}

	/* Readable font sizes */
	.stats-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	.stat-value {
		font-size: 1.375rem;
	}

	.card-title {
		font-size: 1rem;
	}

	/* Table readability */
	.worlds-table {
		font-size: 0.9375rem;
	}

	.worlds-table thead th,
	.worlds-table tbody td {
		padding: 0.75rem 0.5rem;
	}

	/* Hide Seed column to save space */
	.worlds-table th:nth-child(4),
	.worlds-table td:nth-child(4) {
		display: none;
	}
}

@media (max-width: 480px) {
	.admin-main {
		padding: 0.75rem;
	}

	/* Stack header vertically */
	.admin-header {
		flex-direction: column;
		align-items: flex-start;
		gap: 0.5rem;
	}

	.admin-header h1 {
		font-size: 1.125rem;
	}

	.stats-grid {
		grid-template-columns: 1fr;
	}

	/* Full-width stacked buttons */
	.action-group {
		flex-direction: column;
	}

	.action-btn {
		width: 100%;
		justify-content: center;
		padding: 0.625rem 1rem;
	}

	/* Hide Endpoint and Resources columns */
	.worlds-table th:nth-child(3),
	.worlds-table td:nth-child(3),
	.worlds-table th:nth-child(7),
	.worlds-table td:nth-child(7) {
		display: none;
	}
}

/* ============================================
   iPad / Tablet Optimizations (landscape)
   Targets iPad viewports: Mini through Pro 12.9"
   ============================================ */

@media (min-width: 1024px) and (max-width: 1366px) {
	/* Fix header background — force cool dark tone, no warm tint */
	.card-header {
		background: var(--bg-secondary) !important;
		background-color: var(--bg-secondary) !important;
	}

	/* Table: fixed layout, prevent horizontal scroll */
	.worlds-table {
		table-layout: fixed !important;
		min-width: 0 !important;
	}

	/* Reallocate column widths: shrink Endpoint/Seed, expand button columns */
	.worlds-table th:nth-child(1),
	.worlds-table td:nth-child(1) { width: 8%; }   /* Status */
	.worlds-table th:nth-child(2),
	.worlds-table td:nth-child(2) { width: 15%; }  /* World */
	.worlds-table th:nth-child(3),
	.worlds-table td:nth-child(3) { width: 18%; }  /* Actions */
	.worlds-table th:nth-child(4),
	.worlds-table td:nth-child(4) { width: 41%; }  /* Configure */
	.worlds-table th:nth-child(5),
	.worlds-table td:nth-child(5) { width: 18%; }  /* Resources */

	/* Truncate endpoint text — tap to expand */
	.world-endpoint {
		display: block;
		max-width: 100%;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		cursor: pointer;
	}

	.world-endpoint.expanded {
		white-space: normal;
		word-break: break-all;
		overflow: visible;
		text-overflow: clip;
	}

	/* Truncate seed text — tap to expand */
	.world-seed {
		display: block;
		max-width: 100%;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		cursor: pointer;
	}

	.world-seed.expanded {
		white-space: normal;
		word-break: break-all;
		overflow: visible;
		text-overflow: clip;
	}

	/* Prevent horizontal scroll on iPad */
	.worlds-scroll-wrapper,
	.offline-worlds-scroll-wrapper {
		overflow-x: hidden !important;
	}
}

/* ============================================
   Thunderstore Sync Tool Styling
   ============================================ */

.ts-sync-tool {
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.ts-sync-tool.syncing {
	background: rgba(251, 191, 36, 0.2) !important;
}

.ts-sync-icon.spinning {
	animation: spin-icon 1s linear infinite;
}

@keyframes spin-icon {
	from { transform: rotate(0deg); }
	to { transform: rotate(360deg); }
}

.ts-sync-label {
	flex: 1;
}

.ts-sync-stop {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 20px;
	height: 20px;
	padding: 0;
	background: var(--danger);
	border: none;
	border-radius: 3px;
	color: white;
	cursor: pointer;
	margin-left: auto;
	transition: background 0.2s ease, transform 0.1s ease;
}

.ts-sync-stop:hover {
	background: #ef4444;
	transform: scale(1.1);
}

.ts-sync-stop:active {
	transform: scale(0.95);
}

/* ============================================
   World Resource Charts
   ============================================ */

.world-resources {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	min-width: 120px;
}

.world-resource-item {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	font-size: 0.6875rem;
}

.world-resource-item .resource-label {
	color: var(--text-muted);
	font-weight: 500;
	width: 42px;
	flex-shrink: 0;
}

.world-resource-item .resource-value {
	color: var(--text-secondary);
	font-family: var(--font-mono);
	font-size: 0.625rem;
	min-width: 32px;
	text-align: right;
}

.world-load-bar {
	width: 60px;
	height: 6px;
	background: rgba(255,255,255,0.08);
	border-radius: 3px;
	overflow: hidden;
	flex-shrink: 0;
}

.world-load-fill {
	height: 100%;
	border-radius: 3px;
	transition: width 0.4s ease, background-color 0.4s ease;
	background: #4ade80;
}

.world-cpu-chart,
.world-mem-chart {
	flex-shrink: 0;
}

/* ============================================
   Collapsible Table Sections
   ============================================ */

.worlds-section-header {
	cursor: pointer;
	user-select: none;
	transition: background 0.15s ease;
}

.worlds-section-header:hover {
	background: var(--bg-tertiary);
}

.worlds-section-header td {
	padding: 0.75rem 1rem;
	background: var(--bg-tertiary);
	border-bottom: 1px solid var(--border-color);
	border-top: 2px solid var(--accent-primary);
	position: relative;
}

.worlds-section-header td::before {
	content: '';
	display: block;
	position: absolute;
	top: -8px;
	left: 0;
	right: 0;
	height: 8px;
	background: transparent;
}

.worlds-section-toggle {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	font-weight: 600;
	color: var(--text-primary);
}

.worlds-section-toggle svg {
	width: 16px;
	height: 16px;
	transition: transform 0.2s ease;
	color: var(--text-secondary);
}

.worlds-section-header.collapsed .worlds-section-toggle svg {
	transform: rotate(-90deg);
}

.worlds-section-count {
	font-size: 0.75rem;
	font-weight: 500;
	padding: 0.125rem 0.5rem;
	border-radius: 9999px;
	margin-left: 0.5rem;
}

.worlds-section-count.online {
	background: rgba(74, 222, 128, 0.15);
	color: var(--success);
}

.worlds-section-count.offline {
	background: rgba(100, 116, 139, 0.15);
	color: var(--text-muted);
}

.worlds-section-body tr {
	transition: opacity 0.2s ease, max-height 0.2s ease;
}

.worlds-section-body.collapsed tr {
	display: none;
}

/* Scrollable worlds wrapper (used for both Active and Offline) */
.worlds-scroll-wrapper,
.offline-worlds-scroll-wrapper {
	max-height: 375px;
	overflow-y: auto;
	overflow-x: hidden;
	border-bottom: 1px solid var(--border-color);
}

.worlds-scroll-wrapper.collapsed,
.offline-worlds-scroll-wrapper.collapsed {
	display: none;
}

.worlds-scroll-wrapper .worlds-table,
.offline-worlds-scroll-wrapper .offline-worlds-table {
	margin: 0;
	border: none;
}

/* ============================================
   AI Helper Panel
   ============================================ */

.ai-helper-btn {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	padding: 0.375rem 0.75rem;
	background: linear-gradient(135deg, rgba(167, 139, 250, 0.15), rgba(34, 211, 238, 0.15));
	border: 1px solid var(--accent-secondary);
	border-radius: 0.5rem;
	color: var(--accent-secondary);
	font-size: 0.75rem;
	font-weight: 600;
	cursor: pointer;
	transition: all 0.2s ease;
	white-space: nowrap;
}

.ai-helper-btn:hover {
	background: linear-gradient(135deg, rgba(167, 139, 250, 0.25), rgba(34, 211, 238, 0.25));
	color: var(--accent-secondary-hover);
	border-color: var(--accent-secondary-hover);
	transform: translateY(-1px);
}

.ai-helper-btn svg {
	flex-shrink: 0;
}

/* Panel - uses fixed positioning and inset to avoid layout shift */
.ai-panel {
	position: fixed;
	right: 0;
	top: 0;
	bottom: 0;
	width: min(800px, 50vw);
	background: var(--bg-secondary);
	border-left: 2px solid var(--accent-secondary);
	transform: translateX(100%);
	transition: transform 0.3s ease;
	z-index: 200;
	display: flex;
	flex-direction: column;
	box-shadow: -8px 0 32px rgba(167, 139, 250, 0.12), -2px 0 16px rgba(0, 0, 0, 0.4);
	outline: none !important;
	pointer-events: none;
}

.ai-panel.open {
	pointer-events: auto;
}

.ai-panel:focus,
.ai-panel:focus-visible {
	outline: none !important;
	box-shadow: -8px 0 32px rgba(167, 139, 250, 0.12), -2px 0 16px rgba(0, 0, 0, 0.4) !important;
}

.ai-panel.open {
	transform: translateX(0);
}

.ai-panel-overlay {
	display: none;
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.3);
	z-index: 199;
	outline: none !important;
}

.ai-panel-overlay:focus,
.ai-panel-overlay:focus-visible {
	outline: none !important;
}

.ai-panel-overlay.open {
	display: block;
}

/* Panel header */
.ai-panel-header {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.75rem 1rem;
	border-bottom: 1px solid rgba(167, 139, 250, 0.3);
	background: linear-gradient(135deg, rgba(167, 139, 250, 0.08), rgba(34, 211, 238, 0.05));
	flex-shrink: 0;
	flex-wrap: wrap;
}

.ai-panel-selectors {
	display: flex;
	gap: 0.375rem;
	width: 100%;
}

.ai-panel-title {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	font-weight: 600;
	font-size: 0.875rem;
	color: var(--accent-secondary);
	white-space: nowrap;
}

.ai-context-select {
	flex: 1;
	padding: 0.25rem 0.5rem;
	background: var(--bg-input);
	border: 1px solid var(--border-color);
	border-radius: 0.375rem;
	color: var(--text-secondary);
	font-size: 0.75rem;
	cursor: pointer;
	min-width: 0;
}

.ai-context-select:focus {
	outline: none;
	border-color: var(--accent-primary);
}

/* Both selects show orchid when a value is selected */
#aiModelSelect,
#aiContextSelect {
	color: orchid;
}

/* Model dropdown: provider optgroup labels = cyan, model options = orchid */
#aiModelSelect optgroup {
	color: var(--accent-primary);
	font-style: normal;
}
#aiModelSelect option {
	color: orchid;
}

/* Context dropdown: all options orchid, "World Logs" optgroup label = cyan */
#aiContextSelect option {
	color: orchid;
}
#aiContextSelect optgroup {
	color: var(--accent-primary);
	font-style: normal;
}
#aiContextSelect optgroup option {
	color: orchid;
}

.ai-panel-close {
	background: none;
	border: none;
	color: var(--text-muted);
	font-size: 1.25rem;
	cursor: pointer;
	padding: 0.25rem;
	line-height: 1;
	border-radius: 0.25rem;
	transition: color 0.15s ease, background 0.15s ease;
}

.ai-panel-close:hover {
	color: var(--text-primary);
	background: rgba(255, 255, 255, 0.1);
}

/* Messages area */
.ai-panel-messages {
	flex: 1;
	overflow-y: auto;
	padding: 1rem;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	background: linear-gradient(180deg, var(--bg-secondary), rgba(15, 23, 42, 0.95));
	scrollbar-gutter: stable;
}

/* Quick prompts */
.ai-quick-prompts {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	align-items: flex-end;
	padding: 0 1rem;
}

.ai-quick-prompt {
	display: inline-block;
	padding: 0.5rem 0.75rem;
	background: rgba(34, 211, 238, 0.15);
	border: 1px solid rgba(34, 211, 238, 0.3);
	border-radius: 0.5rem;
	color: var(--accent-primary);
	font-size: 0.7rem;
	cursor: pointer;
	transition: all 0.2s ease;
	max-width: 100%;
	word-wrap: break-word;
}

.ai-quick-prompt:hover {
	background: rgba(34, 211, 238, 0.25);
	border-color: rgba(34, 211, 238, 0.5);
}

.ai-message {
	display: flex;
	max-width: 95%;
}

.ai-message.user {
	align-self: flex-end;
}

.ai-message.assistant,
.ai-message.error {
	align-self: flex-start;
}

.ai-message-content {
	padding: 0.625rem 0.875rem;
	border-radius: 0.75rem;
	font-size: 0.8125rem;
	line-height: 1.5;
	word-break: break-word;
}

.ai-message.user .ai-message-content {
	background: linear-gradient(135deg, var(--accent-secondary), #7c3aed) !important;
	color: white !important;
	border-bottom-right-radius: 0.25rem;
}

.ai-message.user .ai-message-content * {
	background: transparent !important;
	color: white !important;
}

.ai-message.assistant .ai-message-content {
	background: linear-gradient(135deg, rgba(51, 65, 85, 0.8), rgba(30, 41, 59, 0.9));
	color: var(--text-primary);
	border: 1px solid rgba(167, 139, 250, 0.1);
	border-bottom-left-radius: 0.25rem;
}

.ai-message.error .ai-message-content {
	background: rgba(248, 113, 113, 0.15);
	color: var(--danger);
	border: 1px solid rgba(248, 113, 113, 0.3);
	border-bottom-left-radius: 0.25rem;
}

.ai-message-content pre {
	background: var(--bg-primary);
	padding: 0.5rem;
	border-radius: 0.375rem;
	overflow-x: auto;
	margin: 0.5rem 0;
	font-size: 0.75rem;
}

.ai-message-content code {
	font-family: var(--font-mono);
	font-size: 0.75rem;
}

.ai-message-content pre code {
	background: none;
	padding: 0;
}

.ai-message-content code:not(pre code) {
	background: rgba(255, 255, 255, 0.1);
	padding: 0.125rem 0.25rem;
	border-radius: 0.25rem;
}

/* Force dark mode styling for HTML content from LLMs */
.ai-message-content * {
	background: transparent !important;
	color: var(--text-primary) !important;
}

.ai-message-content {
	color: var(--text-primary) !important;
	background: linear-gradient(135deg, rgba(51, 65, 85, 0.8), rgba(30, 41, 59, 0.9)) !important;
}

.ai-message-content p,
.ai-message-content div,
.ai-message-content span {
	background: transparent !important;
	color: var(--text-primary) !important;
}

.ai-message-content strong,
.ai-message-content b,
.ai-message-content h1,
.ai-message-content h2,
.ai-message-content h3,
.ai-message-content h4,
.ai-message-content h5,
.ai-message-content h6 {
	color: var(--accent-primary) !important;
	background: transparent !important;
}

.ai-message-content a {
	color: var(--accent-primary) !important;
	background: transparent !important;
}

.ai-message-content ul,
.ai-message-content ol,
.ai-message-content li {
	background: transparent !important;
	color: var(--text-primary) !important;
}

.ai-message-content table {
	background: transparent !important;
	color: var(--text-primary) !important;
}

.ai-message-content th,
.ai-message-content td {
	background: transparent !important;
	color: var(--text-primary) !important;
	border-color: rgba(167, 139, 250, 0.3) !important;
}

.ai-message-content pre {
	background: var(--bg-primary) !important;
	color: var(--text-primary) !important;
	padding: 0.5rem !important;
	border-radius: 0.375rem !important;
	overflow-x: auto !important;
	margin: 0.5rem 0 !important;
	font-size: 0.75rem !important;
}

.ai-message-wrapper {
	display: flex;
	flex-direction: column;
	gap: 0;
}

.ai-message-footer {
	margin-top: 0.5rem;
	padding: 0;
	border-top: none;
	font-size: 0.65rem;
	color: rgba(255, 255, 255, 0.5);
	text-align: center;
	width: 100%;
}

/* Typing indicator */
.ai-typing-dots {
	display: inline-flex;
	gap: 0.25rem;
}

.ai-typing-dots span {
	animation: aiTypingPulse 1.4s infinite;
	font-size: 1.25rem;
	line-height: 1;
	color: var(--text-muted);
}

.ai-typing-dots span:nth-child(2) {
	animation-delay: 0.2s;
}

.ai-typing-dots span:nth-child(3) {
	animation-delay: 0.4s;
}

@keyframes aiTypingPulse {
	0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
	30% { opacity: 1; transform: translateY(-2px); }
}

/* Input area */
.ai-panel-input {
	display: flex;
	gap: 0.5rem;
	padding: 0.75rem 1rem;
	border-top: 1px solid rgba(167, 139, 250, 0.3);
	background: linear-gradient(135deg, rgba(167, 139, 250, 0.08), rgba(34, 211, 238, 0.05));
	flex-shrink: 0;
	align-items: flex-end;
}

.ai-panel-input textarea {
	flex: 1;
	padding: 0.5rem 0.75rem;
	background: var(--bg-input);
	border: 1px solid var(--border-color);
	border-radius: 0.5rem;
	color: var(--text-primary);
	font-size: 0.8125rem;
	font-family: inherit;
	resize: none;
	line-height: 1.4;
	max-height: 120px;
}

.ai-panel-input textarea:focus {
	outline: none;
	border-color: var(--accent-secondary);
}

.ai-panel-input textarea::placeholder {
	color: var(--text-muted);
}

.ai-send-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	padding: 0;
	background: var(--accent-secondary);
	border: none;
	border-radius: 0.5rem;
	color: white;
	cursor: pointer;
	flex-shrink: 0;
	transition: background 0.2s ease, transform 0.1s ease;
}

.ai-send-btn:hover {
	background: var(--accent-secondary-hover);
	transform: scale(1.05);
}

.ai-send-btn:disabled {
	opacity: 0.5;
	cursor: not-allowed;
	transform: none;
}

/* ============================================
   Large Display Optimizations
   Primary target: 3440x1440 | Secondary: 2560x1440
   HiDPI: 2880x1800 @ 175% scale (~1645px CSS viewport)

   Breakpoints account for OS DPI scaling:
     2560x1440 @ 100% scale  → CSS viewport 2560px
     2560x1440 @ 125% scale  → CSS viewport ~2048px  ← caught by 2000px rule
     3440x1440 @ 100% scale  → CSS viewport 3440px
     3440x1440 @ 125% scale  → CSS viewport ~2752px  ← caught by 2560px rule
     2880x1800 @ 175% scale  → CSS viewport ~1645px  ← caught by HiDPI rule

   max-width on .admin-main is the primary fix — it prevents percentage-width
   table columns from expanding into hundreds of pixels of dead whitespace.
   html font-size reduction scales all rem-based elements proportionally.
   ============================================ */

/* HiDPI mid-range: 2880x1800 @ 175% scale
   CSS viewport ≈ 1645px (2880 ÷ 1.75), devicePixelRatio ≥ 1.5
   -webkit-min-device-pixel-ratio covers Safari / older WebKit */
@media (min-width: 1400px) and (max-width: 1999px) and (-webkit-min-device-pixel-ratio: 1.5),
       (min-width: 1400px) and (max-width: 1999px) and (min-resolution: 1.5dppx) {
	.stats-grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/* 2000px+: catches 2560x1440 @ 125% OS scale (~2048px CSS viewport)
   and any viewport ≥ 2000px. Keep default font-size (16px), just
   tighten padding slightly to use extra space efficiently. */
@media (min-width: 2000px) {
	.stats-grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/* 2560px+: catches 2560x1440 @ 100% scale and 3440x1440 @ 125% scale. */
@media (min-width: 2560px) {
	html {
		font-size: 15px;
	}
}

/* 3200px+: catches 3440x1440 @ 100% scale. */
@media (min-width: 3200px) {
	html {
		font-size: 14px;
	}
}

/* ============================================
   PUBLIC PAGE STYLES
   ============================================ */

.public-page {
	padding: 0;
	min-height: 100vh;
	background: var(--bg-primary);
}

.public-container {
	max-width: 1400px;
	margin: 0 auto;
	padding: 1.5rem;
}

/* Public Header */
.public-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 1rem 1.5rem;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	margin-bottom: 1.5rem;
	flex-wrap: wrap;
	gap: 1rem;
}

.public-header-left {
	display: flex;
	align-items: center;
}

.public-header-right {
	display: flex;
	align-items: center;
	gap: 1.5rem;
	flex-wrap: wrap;
}

.public-logo {
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.public-logo-icon {
	width: 40px;
	height: 40px;
	background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
	border-radius: 10px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	font-size: 1rem;
	color: var(--bg-primary);
}

.public-logo-text {
	display: flex;
	flex-direction: column;
}

.public-logo-title {
	font-size: 1.25rem;
	font-weight: 600;
	color: var(--text-primary);
}

.public-logo-version {
	font-size: 0.75rem;
	color: var(--text-muted);
}

.public-user-info {
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.public-user-name {
	color: var(--accent-primary);
	font-weight: 500;
}

.public-user-avatar {
	width: 40px;
	height: 40px;
	border-radius: 50%;
	border: 2px solid var(--accent-primary);
}

.public-download-area {
	display: flex;
	align-items: center;
}

/* Public Stats */
.public-stats {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 2rem;
	padding: 1rem;
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	margin-bottom: 1.5rem;
}

.public-stat-item {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.25rem;
}

.public-stat-value {
	font-size: 1.75rem;
	font-weight: 700;
	color: var(--accent-primary);
}

.public-stat-label {
	font-size: 0.75rem;
	color: var(--text-muted);
	text-transform: uppercase;
	letter-spacing: 0.05em;
}

.public-stat-divider {
	width: 1px;
	height: 40px;
	background: var(--border-color);
}

/* Public Worlds Grid */
.public-worlds-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
	gap: 1.25rem;
}

.public-world-card {
	background: var(--bg-secondary);
	border: 1px solid var(--border-color);
	border-radius: 0.75rem;
	padding: 1.25rem;
	transition: border-color 0.2s ease, transform 0.2s ease;
}

.public-world-card:hover {
	border-color: var(--accent-primary);
	transform: translateY(-2px);
}

.public-world-card.offline {
	opacity: 0.6;
}

.public-world-card.offline:hover {
	border-color: var(--border-color);
	transform: none;
}

.public-world-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 1rem;
}

.public-world-name {
	font-size: 1.125rem;
	font-weight: 600;
	color: var(--text-primary);
	margin: 0;
}

.public-world-status {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	padding: 0.25rem 0.625rem;
	border-radius: 9999px;
	font-size: 0.75rem;
	font-weight: 500;
}

.public-world-status.online {
	background: rgba(74, 222, 128, 0.15);
	color: #4ade80;
}

.public-world-status.offline {
	background: rgba(100, 116, 139, 0.15);
	color: #64748b;
}

.public-world-status .status-dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: currentColor;
}

.public-world-launch {
	margin-bottom: 1rem;
}

.public-launch-btn {
	display: block;
	width: 100%;
	padding: 0.75rem 1rem;
	text-align: center;
	font-size: 0.9375rem;
	font-weight: 600;
	color: var(--bg-primary);
	background: linear-gradient(135deg, var(--success), #22c55e);
	border: none;
	border-radius: 0.5rem;
	text-decoration: none;
	transition: all 0.2s ease;
	cursor: pointer;
}

.public-launch-btn:hover {
	background: linear-gradient(135deg, #22c55e, var(--success));
	transform: translateY(-1px);
	box-shadow: 0 4px 12px rgba(74, 222, 128, 0.3);
	color: var(--bg-primary);
}

.public-launch-btn.disabled {
	background: var(--bg-tertiary);
	color: var(--text-muted);
	cursor: not-allowed;
	pointer-events: none;
}

.public-world-details {
	margin-bottom: 1rem;
	padding: 0.75rem;
	background: var(--bg-primary);
	border-radius: 0.5rem;
}

.public-detail-row {
	display: flex;
	justify-content: space-between;
	padding: 0.375rem 0;
	border-bottom: 1px solid var(--border-light);
}

.public-detail-row:last-child {
	border-bottom: none;
}

.public-detail-label {
	color: var(--text-muted);
	font-size: 0.8125rem;
}

.public-detail-value {
	color: var(--text-primary);
	font-size: 0.8125rem;
	font-family: var(--font-mono);
}

.public-mods-link {
	color: var(--accent-secondary);
	text-decoration: none;
}

.public-mods-link:hover {
	color: var(--success);
}

.public-world-trophies {
	display: flex;
	justify-content: center;
	gap: 0.5rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--border-light);
}

.public-world-trophies img {
	width: 28px;
	height: 28px;
	transition: filter 0.2s ease, transform 0.2s ease;
}

.public-world-trophies img:hover {
	transform: scale(1.15);
}

.public-world-trophies img.dimmed {
	filter: grayscale(100%) opacity(25%);
}

.public-no-worlds {
	grid-column: 1 / -1;
	text-align: center;
	padding: 3rem;
	background: var(--bg-secondary);
	border: 1px dashed var(--border-color);
	border-radius: 0.75rem;
	color: var(--text-secondary);
}

/* Public Warnings */
.public-warnings {
	margin-top: 1.5rem;
}

.public-warning {
	padding: 0.75rem 1rem;
	margin-bottom: 0.5rem;
	background: rgba(248, 113, 113, 0.1);
	border: 1px solid var(--danger);
	border-radius: 0.5rem;
	color: var(--danger);
	font-size: 0.875rem;
}

/* Public footer with social links */
.public-footer {
	position: fixed;
	bottom: 1rem;
	left: 1rem;
	display: flex;
	gap: 0.75rem;
	z-index: 100;
}

/* Cookie Consent Dialog */
.cookie-consent {
	position: fixed;
	bottom: 1.5rem;
	right: 1.5rem;
	z-index: 9999;
	max-width: 380px;
	transform: translateY(calc(100% + 2rem));
	opacity: 0;
	transition: transform 0.4s ease, opacity 0.3s ease;
	pointer-events: none;
}

.cookie-consent.show {
	transform: translateY(0);
	opacity: 1;
	pointer-events: auto;
}

.cookie-consent-content {
	background: #131729;
	border: 1px solid #34e2e2;
	border-radius: 0.75rem;
	padding: 1.25rem;
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 15px rgba(52, 226, 226, 0.08);
	font-family: var(--font-mono-ui);
}

.cookie-consent-header {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-bottom: 0.75rem;
	color: #34e2e2;
	font-size: 1rem;
	font-weight: bold;
}

.cookie-consent-content p {
	color: #d3d7cf;
	font-size: 0.8rem;
	line-height: 1.5;
	margin: 0 0 0.5rem 0;
}

.cookie-consent-legal {
	color: #94a3b8 !important;
	font-size: 0.7rem !important;
	font-style: italic;
}

.cookie-consent-actions {
	display: flex;
	gap: 0.75rem;
	margin-top: 1rem;
}

.cookie-btn {
	flex: 1;
	padding: 0.5rem 1rem;
	border-radius: 0.375rem;
	font-family: var(--font-mono-ui);
	font-size: 0.85rem;
	font-weight: bold;
	cursor: pointer;
	transition: all 0.15s ease;
	border: none;
}

.cookie-btn-accept {
	background: linear-gradient(to bottom, #1a3a2a, #122a1e);
	color: palegreen;
	border: 1px solid #4ade80;
	border-bottom: 3px solid #166534;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(74, 222, 128, 0.1);
}

.cookie-btn-accept:hover {
	background: linear-gradient(to bottom, #224a36, #1a3a2a);
	box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(74, 222, 128, 0.15);
}

.cookie-btn-accept:active {
	transform: translateY(2px);
	border-bottom-width: 1px;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), inset 0 1px 3px rgba(0, 0, 0, 0.2);
}

.cookie-btn-deny {
	background: transparent;
	color: #94a3b8;
	border: 1px solid #475569;
}

.cookie-btn-deny:hover {
	color: #f1f5f9;
	border-color: #94a3b8;
	background: rgba(71, 85, 105, 0.2);
}

.cookie-btn-deny:active {
	transform: translateY(1px);
}

@media (max-width: 480px) {
	.cookie-consent {
		bottom: 1rem;
		right: 1rem;
		left: 1rem;
		max-width: none;
	}
}

/* Public page responsive */
@media (max-width: 768px) {
	.public-header {
		flex-direction: column;
		text-align: center;
	}

	.public-header-left,
	.public-header-right {
		justify-content: center;
	}

	.public-user-info {
		flex-direction: column;
	}

	.public-stats {
		gap: 1rem;
	}

	.public-stat-value {
		font-size: 1.5rem;
	}

	.public-worlds-grid {
		grid-template-columns: 1fr;
	}
}

/* ===== Backup System Styles ===== */

.backup-tab-bar {
	display: flex;
	gap: 0;
	border-bottom: 2px solid var(--border-color);
	padding: 0 1.5rem;
	background: var(--bg-secondary);
}

.backup-tab {
	padding: 0.6rem 1.25rem;
	border: none;
	background: none;
	color: var(--text-muted);
	font-size: 0.85rem;
	font-weight: 600;
	cursor: pointer;
	border-bottom: 2px solid transparent;
	margin-bottom: -2px;
	transition: color 0.2s, border-color 0.2s;
}

.backup-tab:hover {
	color: var(--text-primary);
}

.backup-tab.active {
	color: var(--accent-primary);
	border-bottom-color: var(--accent-primary);
}

.backup-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.8rem;
	table-layout: auto;
}

.backup-table th:nth-child(4),
.backup-table td:nth-child(4) {
	white-space: nowrap;
}

.backup-table th {
	text-align: left;
	padding: 0.5rem 0.5rem;
	font-size: 0.7rem;
	font-weight: 600;
	color: var(--text-muted);
	text-transform: uppercase;
	letter-spacing: 0.05em;
	border-bottom: 1px solid var(--border-color);
}

.backup-table td {
	padding: 0.45rem 0.5rem;
	border-bottom: 1px solid var(--border-light);
	color: var(--text-primary);
	vertical-align: middle;
}

.backup-table tr:hover td {
	background: var(--bg-primary);
}

.backup-table input[type="checkbox"] {
	width: 14px;
	height: 14px;
	cursor: pointer;
}

.backup-actions {
	white-space: nowrap;
}

.backup-badge {
	display: inline-block;
	padding: 0.1rem 0.4rem;
	border-radius: 3px;
	font-size: 0.65rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.03em;
}

.backup-badge-scheduled {
	background: var(--accent-primary);
	color: #fff;
}

.backup-badge-manual {
	background: var(--warning);
	color: #000;
}

.backup-badge-compressed {
	background: var(--success-dark);
	color: #fff;
}

.backup-info-icon {
	display: inline-block;
	cursor: help;
	color: var(--accent-primary);
	font-size: 0.8rem;
	font-style: normal;
	vertical-align: middle;
	opacity: 0.7;
	transition: opacity 0.2s;
	position: relative;
}

.backup-info-icon:hover {
	opacity: 1;
}

/* Tooltips handled by JS — see #phvTooltip in index.php */

.backup-spinner {
	display: inline-block;
	width: 20px;
	height: 20px;
	border: 2px solid var(--border-color);
	border-top-color: var(--accent-primary);
	border-radius: 50%;
	animation: backupSpin 0.8s linear infinite;
}

@keyframes backupSpin {
	to { transform: rotate(360deg); }
}

/* Progress bar */
.backup-progress-track {
	width: 100%;
	height: 6px;
	background: var(--bg-primary);
	border-radius: 3px;
	overflow: hidden;
	margin: 0.75rem 0;
}

.backup-progress-fill {
	height: 100%;
	border-radius: 3px;
	background: var(--accent-primary);
	transition: width 0.4s ease;
	position: relative;
}

.backup-progress-fill.pulsing {
	animation: progressPulse 1.5s ease-in-out infinite;
}

.backup-progress-fill.complete {
	background: var(--success);
	animation: none;
}

.backup-progress-fill.failed {
	background: var(--danger);
	animation: none;
}

@keyframes progressPulse {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.5; }
}

.backup-step {
	display: flex;
	align-items: flex-start;
	gap: 0.5rem;
	padding: 0.3rem 0;
	font-size: 0.8rem;
	color: var(--text-secondary);
}

.backup-step-icon {
	flex-shrink: 0;
	width: 18px;
	text-align: center;
}

.backup-step.active {
	color: var(--text-primary);
	font-weight: 500;
}

.backup-step.active .backup-step-icon {
	animation: progressPulse 1.2s ease-in-out infinite;
}

.backup-step-pct {
	margin-left: auto;
	font-family: var(--font-mono);
	font-size: 0.7rem;
	color: var(--text-muted);
	flex-shrink: 0;
}

/* --- Vanilla world cards (2.40) ---------------------------------------------
   A vanilla world has no mods, no client payload and no boss progression, so its
   card shows connection details instead of a trophy row. It shares .catbox so it
   reads as a peer of the modded cards, not a stripped-down version of one. */
/* Small enough to read as a marker on a row, not as the row's content.
   At 0.7rem with 1.6px/8px padding these came out 23px tall against 20px data rows -- so the
   Access row was the tallest on the card -- and "IN SERVER BROWSER" ran 143px wide, over a
   third of the card and half again the width of the label column. The line-height is set
   explicitly because the page's 1.5 was being inherited straight into the pill's height.
   The tracking stays: it is uppercase, which is the one place tracking earns its keep. */
.catbox .vanilla-badge {
	/* inline-FLEX with a fixed height, not inline-block.
	   As an inline-block the pill's height came from line-height and the text landed wherever
	   the font's metrics put it inside that box -- measured flush against the top with 1.4px
	   spare underneath. Flex centring puts the text in the middle of the box by construction
	   instead of by arithmetic that has to be redone every time the size changes. */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 14px;
	/* baseline alignment sat the pill 2.2px below the centre of the label text next to it --
	   an inline-block aligns its own baseline to the row's, which for a bordered box means it
	   hangs. `middle` lines its centre up with the text it sits beside. */
	vertical-align: middle;
	/* `middle` gets it most of the way (2.20px low -> 1.16px); the rest is that it aligns to the
	   parent's baseline plus half its x-height, which is not the same point as the optical
	   centre of the label text beside it. Measured, not guessed -- see test-access-badges.php,
	   which fails if either offset drifts past half a pixel. */
	position: relative;
	top: -1px;
	/* INTEGER px, not rem/em. Every value here used to be fractional -- padding 0.4rem = 6.4px,
	   font-size 0.6rem = 9.6px, letter-spacing 0.04em = 0.384px -- and a fractional box cannot
	   land on the pixel grid the same way on both sides. Measured with
	   dev_tools/measure-pill-centering.js, the old rule put the ink dead centre at DPR 1 and 1
	   to 2 device px LEFT of centre at DPR 1.25/1.5/2, which is 125%/150% display scaling: the
	   common case, and the reason this pill kept being reported as off-centre while measuring
	   as centred. Integer values halve the worst case and are exact at DPR 1, 1.25 and 2.
	   The residual 1px at DPR 1.5 is glyph side bearing, which no padding rule can remove. */
	padding: 0 6px;
	margin-right: 0.25rem;
	border-radius: 0.625rem;
	font-size: 10px;
	line-height: 1;
	text-transform: uppercase;
	/* letter-spacing adds its gap AFTER the last character too, so the text box is one tracking
	   unit wider than the ink. The previous rule subtracted that unit from padding-right;
	   measured across five DPRs that compensation changed nothing (candidates E and F scored
	   identically), because 0.5px cannot survive pixel snapping either. Symmetric padding is
	   simpler and measures the same, so the compensation is gone rather than kept as decoration. */
	letter-spacing: 0.5px;
	background: var(--accent-primary, #4a8);
	color: #0b0b0b;
}


.catbox-vanilla .world-endpoint code {
	font-size: 0.85em;
	word-break: break-all;
}

.catbox-vanilla .vanilla-password-mask {
	font-family: monospace;
	letter-spacing: 0.1em;
	margin-right: 0.4rem;
}

.catbox-vanilla .vanilla-password-action {
	font-size: 0.72rem;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	text-decoration: none;
	opacity: 0.75;
	margin-left: 0.45rem;
}

.catbox-vanilla .vanilla-password-action:hover {
	opacity: 1;
	text-decoration: underline;
}

.catbox-vanilla .vanilla-password-copied {
	color: var(--success, #4ade80);
	opacity: 1;
}

.catbox-vanilla .vanilla-password-failed {
	color: var(--danger, #f87171);
	opacity: 1;
}

.catbox-vanilla .vanilla-hint {
	padding: 0.6rem 0.5rem 0;
	font-size: 0.72rem;
	line-height: 1.35;
	text-align: center;
	color: var(--text-muted, #888);
}

/* Card rows take their NATURAL height; one empty row soaks up whatever is left over.
   The card table is height:100%, which keeps the table filling the card. Without somewhere for
   the extra height to go, it was shared across every row -- so an online modded card sharing a
   flex row with a taller vanilla card had its rows pushed 112px apart while the offline cards
   next to it sat at 16px. Same card, different neighbours, different spacing.
   height:1px on a table cell is a MINIMUM, not a maximum: the cell still sizes to its text and
   simply stops claiming a share of the slack. .card-slack is the opposite -- it asks for all of
   it, so the leftover collects in one empty row instead of being spread through the data.
   The hint sits AFTER that row, which puts it at the bottom of the card, the same place the
   boss trophies sit on a modded card. It is inside the table too: as a sibling div after a
   100%-height table it was laid out past the bottom of the card and clipped. */
.catbox th.card_worldName,
.catbox th.card_worldLaunch,
.catbox td.card_worldInfo,
.catbox td.vanilla-hint {
	height: 1px;
}

/* ...and they are made real table cells again.
   display:block / inline-block on a <td> takes it out of table layout, so the Server row was
   laid out as an inline box in the row's anonymous block and came out 30px tall against a 21px
   rhythm -- one row visibly bulging in an otherwise even column. As a table cell it sits on the
   rhythm like everything else, and overflow:visible stops it clipping its own text. */
.catbox td.world-seed,
.catbox td.world-endpoint {
	display: table-cell !important;
	overflow: visible !important;
	background: transparent !important;
}

/* Why that rule above needs this one.
   .world-seed and .world-endpoint are written for an inline element -- display:block /
   inline-block plus overflow:hidden -- but on a world card those classes land on the <td>
   ITSELF. A cell that is not display:table-cell clips its own content, and a pinned
   height:1px then hid the value outright: every card rendered "Seed :" and "Server :" with
   nothing after the colon while every other row looked fine.
   The first fix excluded those two cells from the height pin. Restoring display:table-cell is
   the better one: height on a real table cell is a MINIMUM, so the pin cannot squash them and
   the exclusion is no longer needed. */

/* The label column is the same width on every card.
   The table is width:100%, so with auto layout the leftover width is handed to whichever
   column can take it -- meaning a card whose values are short (an undeployed world with no
   MD5, no date) gave its label column 226px while a card with a long MD5 hash gave it 95px.
   The labels are identical text, so the distance from the colon to the value jumped around
   from card to card. width:1px is a shrink-to-fit request, not a literal width: the cell
   takes its content's width and no more. Every label is padded with &nbsp; to the same 11
   monospace characters, so every card now lines up with every other one. */
.catbox td.card_worldInfo:first-child {
	width: 1px;
	white-space: nowrap;
}

/* The card stacks its two tables -- the data table, then the boss trophies -- as flex items.
   As plain blocks the data table's height:100% claimed the whole content box and the trophy
   table was laid out below it, hanging off the bottom of the card; only an outsized
   padding-bottom hid the overflow. As a flex column the data table takes the leftover
   (flex: 1) and the trophy row contributes its own height, so the card is as tall as what is
   actually in it. */
body:has(.google_header) .catbox {
	display: flex !important;
	flex-direction: column !important;
}

body:has(.google_header) .catbox > table:first-of-type {
	flex: 1 1 auto !important;
}

body:has(.google_header) .catbox > table.trophy-table {
	flex: 0 0 auto !important;
	/* Separation from the last data row. It used to come free from the 50px padding-bottom
	   that the trophies were overflowing into; now that they sit on the card properly it has
	   to be asked for. */
	margin-top: 8px !important;
}

.catbox td.card-slack {
	height: 100%;
	padding: 0;
}

/* The breather between the Launch line and the first data row. It used to be
   `<td style='height: 12px;'</td>` -- a tag that never closes, so the parser read `</td` as an
   attribute name. Same gap, as a real cell. */
.catbox td.card-gap {
	height: 6px;
	padding: 0;
}

/* ---- Card height trim --------------------------------------------------------------------
   The page sets line-height: 1.5 on everything. That is the right figure for BODY text and it
   is what fixed the squashed look, but it is not meant for single-line headings or for a fixed
   tabular row, and applying it everywhere was costing the card ~25px of pure leading.
   The name and the Launch line are one line each: 1.2 is normal for a heading.
   The data rows go to 20px -- still 1.43 on a 14px face, inside the 1.4-1.6 range for dense
   rows, and a whole number so the rhythm stays on exact pixels. */
body:has(.google_header) .catbox .card_worldName {
	line-height: 1.2 !important;
}

body:has(.google_header) .catbox .card_worldLaunch {
	line-height: 1.2 !important;
}

body:has(.google_header) .catbox td.card_worldInfo {
	line-height: 20px !important;
}

/* The hint is real prose and keeps a readable figure, just not the full 1.5. */
body:has(.google_header) .catbox td.vanilla-hint {
	line-height: 1.4 !important;
}

/* Breathing room under the world name, and real padding on the hint.
   Both need !important, and not for style: the authenticated page carries a blanket
   `body:has(.google_header) th, td { padding: 0 !important }`, which silently beats any
   specificity these selectors can reach. The hint's own padding rule further up has never
   applied for exactly that reason. Scoped the same way so it wins on equal terms. */
body:has(.google_header) .catbox th.card_worldName {
	/* 0.75rem (an 11px gap) read as two unrelated lines rather than a heading and its
	   subtitle. 0.5rem gives 7px: still clearly two lines, but they belong together.
	   0.375rem was tried first and closed it to 5px, which is too tight to be deliberate. */
	padding-bottom: 0.5rem !important;
}

/* Shrink-to-fit label cells sit flush against their value, because the gap used to be the
   leftover width the column was absorbing. Same blanket-padding problem, same !important. */
body:has(.google_header) .catbox td.card_worldInfo:first-child {
	padding-right: 0.5rem !important;
}

body:has(.google_header) .catbox td.vanilla-hint {
	padding: 0.5rem 0.5rem 0 !important;
}

/* An offline vanilla card must grey out exactly like an offline modded card.
   The badges set their own background, so opacity alone would still leave a tinted
   pill sitting on an otherwise grey card — the colour has to be replaced, not faded. */
.catbox .vanilla-badge-dimmed,
.catbox.card_dimmed .vanilla-badge {
	background: var(--bg-tertiary, #333) !important;
	color: var(--text-muted, #888) !important;
	opacity: 0.6;
}

.catbox-vanilla.card_dimmed .world-endpoint code,
.catbox-vanilla.card_dimmed .vanilla-password-action {
	color: var(--text-muted, #888) !important;
	opacity: 0.6;
}

/* =====================================================================
   SETTINGS / FORM COMPONENT LAYER  (added 2026-09-09)
   =====================================================================

   Why this exists: index.php carried 525 inline style="" attributes, most of
   them hand-tuning padding and widths inside a JS template literal. Every
   settings row was styled from scratch, so nothing lined up with anything
   else and there was no single place to fix spacing. These classes replace
   that. Prefix pv- so they cannot collide with Bootstrap, which this admin
   also loads.

   Measured faults this addresses (see dev_tools/ux-audit.js):
     - 25 controls under the 30px hit target on new_world.php, 24 on edit_world
     - .switch shipped at 36x20; 24px is the accessible floor for a toggle
     - horizontal page scroll at 1280 (1300 > 1280)
     - the world settings modal clipped its last row with no scroll affordance
   ------------------------------------------------------------------ */

/* --- layout overflow -------------------------------------------------
   .admin-layout carries margin:-1rem to cancel body padding, while
   .admin-main is flex:1 AND margin-left:240px for the fixed sidebar. A flex
   child defaults to min-width:auto, so it refuses to shrink below its content
   and the two widths double-count -- that was the 20px of horizontal scroll. */
.admin-main {
	min-width: 0;
}
.admin-layout {
	max-width: 100vw;
	overflow-x: clip;
}

/* --- settings sections ---------------------------------------------- */
.pv-section {
	margin-bottom: 1.5rem;
}
.pv-section:last-child {
	margin-bottom: 0;
}
.pv-section-title {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0 0 0.75rem 0;
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-secondary);
}
.pv-section-title::before {
	content: "";
	width: 3px;
	height: 0.85em;
	border-radius: 2px;
	background: var(--accent-primary);
}
.pv-section-hint {
	margin: -0.4rem 0 0.75rem 0;
	font-size: 0.78rem;
	line-height: 1.45;
	color: var(--text-muted);
}
/* The -0.4rem above is for a hint that sits under a section TITLE, where it should
   read as part of the heading. A hint that FOLLOWS a panel is a different thing --
   it comments on the group above it, and the negative margin jammed it against the
   panel border. Give it real air instead. */
.pv-panel + .pv-section-hint {
	margin-top: 0.7rem;
}
/* A hint living INSIDE the actions bar sits on the same line as the button rather
   than stranded below it. It takes the free space, so the status text no longer
   needs to (two `margin-right: auto` in one flex row would split the gap and park
   the status in the middle). */
.pv-actions > .pv-actions-hint {
	margin: 0 auto 0 0;
	flex: 1 1 16rem;
	text-align: left;
}
.pv-actions > .pv-actions-hint ~ .pv-status {
	margin-right: 0;
}

/* Panel that groups rows. One border around a group beats one card per item --
   the old modal drew a full card for a single toggle, which is why everything
   ended up below the fold. */
.pv-panel {
	background: var(--bg-primary);
	border: 1px solid var(--border-light, var(--border-color));
	border-radius: 0.5rem;
	overflow: hidden;
}

/* --- key/value read-only rows --------------------------------------- */
.pv-kv {
	display: grid;
	grid-template-columns: minmax(7rem, auto) 1fr;
	gap: 0.15rem 1rem;
	padding: 0.6rem 0.9rem;
	align-items: baseline;
	border-bottom: 1px solid var(--border-light, var(--border-color));
}
.pv-kv:last-child {
	border-bottom: 0;
}
.pv-kv > dt,
.pv-kv-key {
	font-size: 0.82rem;
	color: var(--text-secondary);
	margin: 0;
}
.pv-kv > dd,
.pv-kv-val {
	margin: 0;
	text-align: right;
	font-size: 0.82rem;
	overflow-wrap: anywhere;   /* an MD5 must wrap, not widen the dialog */
}

/* --- a labelled control row (label + description on the left, control right) */
.pv-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.7rem 0.9rem;
	border-bottom: 1px solid var(--border-light, var(--border-color));
}
.pv-row:last-child {
	border-bottom: 0;
}
.pv-row-text {
	min-width: 0;          /* lets long descriptions wrap instead of pushing the control out */
}
.pv-row-label {
	display: block;
	font-size: 0.9rem;
	margin-bottom: 0.1rem;
}
.pv-row-desc {
	display: block;
	font-size: 0.75rem;
	line-height: 1.45;
	color: var(--text-muted);
}
.pv-row-control {
	flex: 0 0 auto;
}
/* stack the control under the text when the row gets narrow */
.pv-row.pv-row-stack {
	flex-direction: column;
	align-items: stretch;
}
.pv-row.pv-row-stack .pv-row-control {
	margin-top: 0.5rem;
}

/* --- form fields ----------------------------------------------------- */
.pv-field {
	display: block;
	margin-bottom: 0.85rem;
}
.pv-field:last-child {
	margin-bottom: 0;
}
.pv-field-label {
	display: block;
	margin-bottom: 0.3rem;
	font-size: 0.82rem;
	color: var(--text-secondary);
}
.pv-field-hint {
	display: block;
	margin-top: 0.3rem;
	font-size: 0.73rem;
	line-height: 1.45;
	color: var(--text-muted);
}

/* Responsive field grid: two up when there is room, one up when there is not.
   auto-fit + minmax is what stops these from overflowing on a narrow window,
   which fixed-width columns did not. */
.pv-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 0.85rem 1rem;
}
.pv-grid-wide {
	grid-column: 1 / -1;
}

/* Consistent control sizing. 34px keeps text controls a comfortable target
   without looking oversized next to the existing dense tables. */
.pv-input,
.pv-panel .form-control,
.pv-field .form-control {
	width: 100%;
	min-height: 34px;
	padding: 0.4rem 0.6rem;
	font-size: 0.85rem;
	box-sizing: border-box;
}
textarea.pv-input,
.pv-field textarea.form-control {
	min-height: 110px;
	font-family: var(--font-mono, monospace);
	line-height: 1.5;
	resize: vertical;
}

/* --- toggle switch ---------------------------------------------------
   REMOVED: this used to bump the settings-modal switches to 44x24 with an 18px knob, on the
   grounds that the base 36x20 was under the published minimum for a toggle. The result was a
   control noticeably larger than anything around it -- it read as the loudest thing in a panel
   of quiet rows -- and a second switch size to keep in step with the first.
   Every switch is now the one size defined by .switch (32x18). The hit-area argument was the
   right one and is answered properly, by .slider::after extending the CLICKABLE box to 36x24
   without extending the drawn pill. */

/* --- action bar ------------------------------------------------------ */
.pv-actions {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	gap: 0.6rem;
	flex-wrap: wrap;
	margin-top: 0.85rem;
}
.pv-actions .pv-status {
	margin-right: auto;
	font-size: 0.82rem;
	min-height: 1.2em;
}
.pv-actions .action-btn {
	min-height: 34px;
}

/* --- callout --------------------------------------------------------- */
.pv-note {
	border-left: 3px solid var(--accent-primary);
	background: var(--bg-primary);
	border-radius: 0.375rem;
	padding: 0.7rem 0.9rem;
	margin-bottom: 0.85rem;
}
.pv-note p {
	margin: 0 0 0.35rem 0;
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--text-secondary);
}
.pv-note p:last-child {
	margin-bottom: 0;
}
.pv-note code,
.pv-note kbd {
	background: var(--bg-tertiary);
	border-radius: 0.2rem;
	padding: 0 0.3rem;
	font-size: 0.95em;
}

/* --- access lists ---------------------------------------------------
   The Access tab carries three peer lists (Citizens / Admins / Banned). It used
   to repeat the same "how to find an ID" note above each one. Three copies of a
   five-line explanation is noise on screen, and a screen reader reads the whole
   thing out three times -- GOV.UK's guidance is that an explanation this long is
   not hint text at all. It now appears once, as a disclosure, above all three.

   Stacked sections rather than tabs, deliberately: the common question is "is
   this player on more than one of these?", and tabs are the wrong control when
   the user has to compare across the groups. */
.pv-disclosure {
	background: var(--bg-primary);
	border: 1px solid var(--border-light);
	border-radius: 0.375rem;
	margin-bottom: 1.25rem;
}
.pv-disclosure > summary {
	cursor: pointer;
	padding: 0.6rem 0.9rem;
	font-size: 0.8rem;
	font-weight: 600;
	color: var(--text-secondary);
	list-style: none;
	display: flex;
	align-items: center;
	gap: 0.45rem;
}
.pv-disclosure > summary::-webkit-details-marker {
	display: none;
}
/* Own marker, so the affordance survives the list-style reset above. */
.pv-disclosure > summary::before {
	content: "▸";
	font-size: 0.7rem;
	transition: transform 0.15s ease;
	color: var(--accent-primary);
}
.pv-disclosure[open] > summary::before {
	transform: rotate(90deg);
}
.pv-disclosure > summary:hover {
	color: var(--text-primary);
}
.pv-disclosure .pv-disclosure-body {
	padding: 0 0.9rem 0.85rem;
}
.pv-disclosure .pv-disclosure-body p {
	margin: 0 0 0.4rem 0;
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--text-secondary);
}
.pv-disclosure .pv-disclosure-body p:last-child {
	margin-bottom: 0;
}
.pv-disclosure code,
.pv-disclosure kbd {
	background: var(--bg-tertiary);
	border-radius: 0.2rem;
	padding: 0 0.3rem;
	font-size: 0.95em;
}

/* Count badge in a list heading. Cheap information scent -- "Banned 0" and
   "Banned 14" are very different screens and used to look identical until you
   read the textarea. */
.pv-count {
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	border-radius: 999px;
	padding: 0.05rem 0.45rem;
	font-size: 0.7rem;
	font-weight: 600;
	letter-spacing: 0;
}

/* Banned is the one list here with a destructive edge, so it gets a warning
   line -- not a red panel. Colouring the whole section would compete with the
   danger save button for the same signal. */
.pv-warn {
	border-left: 3px solid var(--warning);
	background: var(--bg-primary);
	border-radius: 0.375rem;
	padding: 0.55rem 0.8rem;
	margin-bottom: 0.85rem;
	font-size: 0.78rem;
	line-height: 1.5;
	color: var(--text-secondary);
}

/* Per-list footer: lookup on the left, save on the right. */
.pv-list-actions {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	justify-content: flex-end;
	padding-top: 0.85rem;
	border-top: 1px solid var(--border-light);
}
.pv-list-actions .pv-list-lookup {
	margin-right: auto;
}
.pv-list-status {
	text-align: right;
	margin-top: 0.6rem;
	font-size: 0.8rem;
	min-height: 1.1em;
}
.pv-list-area {
	width: 100%;
	min-height: 120px;
	font-family: var(--font-mono);
	font-size: 0.875rem;
	resize: vertical;
}

/* --- modal shell ----------------------------------------------------
   The world settings dialog was clipping its last row with no affordance that
   more existed. Make the dialog a flex column with a fixed header and tab bar
   and a single scrolling body, capped at the viewport. */
.mods-modal {
	display: flex;
	flex-direction: column;
	max-height: min(90vh, 900px);
}
.mods-modal > .mods-modal-header,
.mods-modal > .backup-tab-bar {
	flex: 0 0 auto;
}
.mods-modal > .mods-modal-body {
	flex: 1 1 auto;
	overflow-y: auto;
	overscroll-behavior: contain;
}

/* Bootstrap .row uses negative side margins and expects a padded .col parent.
   Dropped into a plain div it escapes by 12px a side -- measured on the server
   settings dialog. Neutralise it inside our panels. */
.pv-panel > .row,
.pv-section > .row {
	margin-left: 0;
	margin-right: 0;
}

/* --- Bootstrap .form-check, repaired --------------------------------
   Two faults, both visible on new_world.php:

   1. No dark-theme appearance. The inputs rendered as the browser default
      white rectangle on a dark card.
   2. The input sat ON TOP of its label. Bootstrap indents .form-check by
      1.5em and pulls the input back with a negative margin; the override
      above forces width/height with !important, and without the indent the
      box and the text occupied the same space. That is the "switches and
      labels overlap" report.

   Laying the row out with flex removes the dependency on Bootstrap's negative
   margin entirely, and lets the help text sit on its own line under the label
   instead of being squeezed alongside it. */
.form-check {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0 0.55rem;
	padding-left: 0;
	margin-bottom: 0.6rem;
}
.form-check > .form-check-input {
	order: 0;
	margin-left: 0;
	margin-top: 0;
	flex: 0 0 auto;
}
.form-check > .form-check-label {
	order: 1;
	cursor: pointer;
	margin-bottom: 0;
}
/* Help text belongs under the pair, indented to line up with the label. */
.form-check > .form-text {
	order: 2;
	flex: 0 0 100%;
	margin-top: 0.2rem;
	padding-left: calc(1.05em + 0.55rem);
	font-size: 0.78rem;
	line-height: 1.5;
}

/* Native controls, themed. accent-color does the heavy lifting: it recolours
   the check/dot without replacing the control, so keyboard focus, indeterminate
   state and the platform's own hit handling all keep working. */
.admin-main input[type="checkbox"],
.admin-main input[type="radio"],
.mods-modal input[type="checkbox"],
.mods-modal input[type="radio"] {
	accent-color: var(--accent-primary);
	cursor: pointer;
}
/* A bare 1em checkbox is a small target. Nudge it up and let the label carry
   the rest -- clicking the label already toggles these. */
.form-check > .form-check-input[type="checkbox"],
.form-check > .form-check-input[type="radio"] {
	width: 1.05em !important;
	height: 1.05em !important;
	min-width: 1.05em !important;
	min-height: 1.05em !important;
}

/* Mod tables: give the select column's checkbox room so it is not flush
   against the row edge. */
table.dataTable td input[type="checkbox"] {
	width: 1.05em;
	height: 1.05em;
	margin: 0;
	vertical-align: middle;
}

/* --- create/edit world forms ---------------------------------------- */
.admin-main .form-control,
.admin-main .form-select {
	min-height: 34px;
}
/* Bootstrap rows inside our cards escape by 12px a side; these pages put .row
   straight inside a padded panel rather than a .container. */
.admin-main .card-panel > .row,
.admin-main .world-form-card > .row {
	margin-left: 0;
	margin-right: 0;
}

/* --- checkbox / radio appearance ------------------------------------
   Bootstrap sets appearance:none on .form-check-input and then draws a
   light-theme box: white fill, #dee2e6 border. On these dark cards that
   rendered as a bare white rectangle.
   Since the control is already appearance:none, accent-color cannot help --
   we have to draw it. Explicit px rather than em: the em-based rules that
   were already here were not producing the size they declared, and a fixed
   px box is one less thing to reason about. */
.form-check-input[type="checkbox"],
.form-check-input[type="radio"] {
	-webkit-appearance: none !important;
	appearance: none !important;
	box-sizing: border-box !important;
	width: 17px !important;
	height: 17px !important;
	min-width: 17px !important;
	min-height: 17px !important;
	max-width: 17px !important;
	max-height: 17px !important;
	padding: 0 !important;
	margin: 0;
	flex: 0 0 auto;
	background-color: var(--bg-tertiary, #334155) !important;
	background-image: none !important;
	border: 1px solid var(--border-color, #475569) !important;
	position: relative;
	cursor: pointer;
	transition: background-color 0.15s ease, border-color 0.15s ease;
}
.form-check-input[type="checkbox"] {
	border-radius: 4px !important;
}
.form-check-input[type="radio"] {
	border-radius: 50% !important;
}
.form-check-input:hover {
	border-color: var(--accent-primary) !important;
}
.form-check-input:focus-visible {
	outline: 2px solid var(--accent-primary);
	outline-offset: 2px;
}
.form-check-input:checked {
	background-color: var(--accent-primary) !important;
	border-color: var(--accent-primary) !important;
}
/* the tick, drawn rather than a background-image so it inherits sizing */
.form-check-input[type="checkbox"]:checked::after {
	content: "";
	position: absolute;
	left: 5px;
	top: 1px;
	width: 4px;
	height: 9px;
	border: solid var(--bg-primary, #0f172a);
	border-width: 0 2px 2px 0;
	transform: rotate(45deg);
}
.form-check-input[type="radio"]:checked::after {
	content: "";
	position: absolute;
	inset: 3px;
	border-radius: 50%;
	background: var(--bg-primary, #0f172a);
}

/* Same treatment for bare checkboxes that carry no Bootstrap class -- the
   "Also clone ..." options on new_world.php are plain <input type=checkbox>
   and were still rendering as white boxes after the .form-check-input fix. */
body input[type="checkbox"]:not(.form-check-input),
body input[type="radio"]:not(.form-check-input),
.mods-modal input[type="checkbox"]:not(.form-check-input):not(.switch input),
.mods-modal input[type="radio"]:not(.form-check-input) {
	-webkit-appearance: none;
	appearance: none;
	box-sizing: border-box;
	width: 16px;
	height: 16px;
	min-width: 16px;
	padding: 0;
	vertical-align: -2px;
	background-color: var(--bg-tertiary, #334155);
	border: 1px solid var(--border-color, #475569);
	border-radius: 4px;
	position: relative;
	cursor: pointer;
}
body input[type="radio"]:not(.form-check-input),
.mods-modal input[type="radio"]:not(.form-check-input) {
	border-radius: 50%;
}
body input[type="checkbox"]:not(.form-check-input):checked,
body input[type="radio"]:not(.form-check-input):checked,
.mods-modal input[type="checkbox"]:not(.form-check-input):checked,
.mods-modal input[type="radio"]:not(.form-check-input):checked {
	background-color: var(--accent-primary);
	border-color: var(--accent-primary);
}
body input[type="checkbox"]:not(.form-check-input):checked::after,
.mods-modal input[type="checkbox"]:not(.form-check-input):checked::after {
	content: "";
	position: absolute;
	left: 4.5px;
	top: 1px;
	width: 4px;
	height: 8px;
	border: solid var(--bg-primary, #0f172a);
	border-width: 0 2px 2px 0;
	transform: rotate(45deg);
}
/* The .switch component draws its own knob -- never restyle its hidden input. */
.switch input[type="checkbox"] {
	appearance: none !important;
	width: 0 !important;
	height: 0 !important;
	min-width: 0 !important;
	opacity: 0;
	border: 0 !important;
	background: none !important;
}

/* --- mod table tabs + sticky action bar (new_world / edit_world) ------
   The Selected and Available mod tables were stacked, each with its own
   DataTables "Show N entries" and "Search" chrome, and Available holds ~9,000
   rows. That put two sets of table controls on screen at once and pushed the
   submit buttons a long way below the fold -- which is why the page had a
   second "Create World" button at the top, duplicating the primary action. */
.pv-tabbar {
	display: flex;
	gap: 0.25rem;
	border-bottom: 2px solid var(--accent-primary);
	background: var(--bg-tertiary);
	padding: 0 0.5rem;
}
.pv-tab {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	border: 0;
	background: none;
	color: var(--text-secondary);
	font-weight: 600;
	font-size: 0.9rem;
	padding: 0.65rem 1rem;
	margin-bottom: -2px;
	border-bottom: 2px solid transparent;
	cursor: pointer;
	transition: color 0.15s ease, border-color 0.15s ease;
}
.pv-tab:hover {
	color: var(--text-primary);
}
.pv-tab.active {
	color: var(--accent-primary);
	border-bottom-color: var(--accent-primary);
}

/* Keeps the primary action reachable from anywhere in a very long form. */
.pv-stickybar {
	position: sticky;
	bottom: 0;
	z-index: 20;
	display: flex;
	justify-content: center;
	gap: 0.75rem;
	padding: 0.85rem 1rem;
	margin-top: 1.5rem;
	background: var(--bg-secondary);
	border-top: 1px solid var(--accent-primary);
	box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.35);
}

/* Crossplay join modal. Reuses the macOS-install modal skin so there is one modal look on the
   public page; only the width and the step list are specific to it. Kept narrow -- it carries
   one code and three lines, and at the install modal's width the text ran to a single short
   line per row and read as an error dialog rather than an instruction. */
/* This page carries a hand-rolled `.modal` rule from before Bootstrap: it makes .modal ITSELF
   the dim overlay (dark background + blur) at z-index 1050. Bootstrap instead wants .modal
   transparent and ABOVE its own .modal-backdrop (1055 vs 1050). With both at 1050 the backdrop
   wins the tie on DOM order, so it paints OVER the dialog -- the modal looked dimmed AND every
   click, including Close, landed on the backdrop instead of the button. Verified with
   elementFromPoint over the Close button: it returned .modal-backdrop.
   Same scoped fix #macInstallModal already uses: sit above the backdrop and bring our own dim. */
#crossplayJoinModal.modal {
	z-index: 9999;
	background-color: rgba(0, 0, 0, 0.6);
	backdrop-filter: blur(6px);
}
.crossplay-join-dialog {
	max-width: 420px;
}
.crossplay-join-steps {
	margin: 0.9rem 0 0.6rem;
	padding-left: 1.2rem;
	font-size: 0.85rem;
	line-height: 1.6;
	color: var(--text-secondary, #aaa);
}
.crossplay-join-steps li + li {
	margin-top: 0.2rem;
}
/* The join code is the point of the dialog, so it gets the emphasis the install command has. */
#crossplayJoinCode {
	letter-spacing: 0.08em;
	font-size: 1.05rem;
}
