/* ═══════════════════════════════════════════════════════════════════════
   Phosphor Refined app shell — full-height sidebar + content column.
   Mirrors prototype app.jsx: position:absolute; inset:0; display:flex.
   ═══════════════════════════════════════════════════════════════════════ */
.phosphor-shell {
	position: absolute;
	inset: 0;
	display: flex;
	overflow: hidden;
	background: var(--bg);
	background-image: var(--mud-palette-background-gradient);
}

/* The page texture (ThemeTexture, css/themes/layers.css) is pinned to the window over the page's surface; the
   rail, the section sidebar, the widget asides and the page content are raised above it. The sidebar's surface
   is mostly opaque, so the texture shows through it only faintly and its labels stay easy to read. */
.phosphor-rail,
.phosphor-content-col,
.phosphor-widget-aside {
	position: relative;
	z-index: 1;
}


/* A theme's letterbox: bars across the top and bottom of the window right of the rail, --letterbox-h high (none
   unless a theme sets it), drawn by --letterbox-bg's background layers. They lie over the page texture and under
   the section sidebar and the page, so the sidebar sliding in and out passes over them and they never move. The
   main column leaves room for them. Desktop only. */
.phosphor-shell::after {
	content: "";
	position: absolute;
	inset: 0 0 0 var(--rail-w);
	z-index: 0;
	pointer-events: none;
	background: var(--letterbox-bg, none);
}

/* Content column: topbar + scrollable main + footer/terminal */
.phosphor-content-col {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	position: relative;
}

/* ── Topbar: glass-morphism, spans content column only ── */
.phosphor-topbar {
	height: var(--topbar-h);
	flex-shrink: 0;
	display: none; /* touch chrome only (README §10 Q1: no desktop top bar) */
	align-items: center;
	gap: var(--space-4);
	padding: 0 var(--space-5);
	border-bottom: 1px solid var(--border-soft);
	background: color-mix(in srgb, var(--surface-3) 80%, transparent);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	position: relative;
	z-index: 5;
}

/* The game's logo and name, a link to Home. It takes the room between the left and right buttons and
   shortens the name with an ellipsis when that room runs out. */
.phosphor-topbar-game {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: var(--space-3);
	color: var(--text);
	text-decoration: none;
}

.phosphor-topbar-game > svg,
.phosphor-topbar-game > img {
	flex: none;
}

.phosphor-topbar-name {
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	font-family: var(--font-display);
	font-weight: var(--font-title-weight);
	font-size: var(--text-base);
}

/* ── Topbar icon buttons (menu, terminal, close) ── */
.phosphor-icon-btn {
	width: 38px;
	height: 38px;
	display: grid;
	place-items: center;
	border-radius: var(--radius);
	background: transparent;
	border: none;
	cursor: pointer;
	color: var(--text-dim);
	transition: background var(--motion-fade), color var(--motion-fade);
	flex-shrink: 0;
	padding: 0;
}
.phosphor-icon-btn:hover {
	background: var(--surface-2);
	color: var(--text);
}
.phosphor-icon-btn--active,
.phosphor-icon-btn--active:hover {
	color: var(--accent);
	background: rgba(var(--glow), 0.12);
}

/* ── The global TopBar zone (Q1): a slim strip above main, rendered only when it has widgets ── */
.phosphor-topzone {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: var(--space-3);
	min-height: 44px;
	padding: var(--space-3) var(--space-5);
	border-bottom: 1px solid var(--border-soft);
	background: var(--surface-3);
}

/* ── Scrollable main content ── */
.phosphor-main {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	/* Room for a theme's letterbox (.phosphor-shell::after), so the page scrolls between its bars. */
	border-block: var(--letterbox-h, 0px) solid transparent;
}
/* The page takes the whole content column at every width, so a page's left edge always sits against the
   sidebar. A page that reads better narrower caps its own block (a form, an article's measure). */
.phosphor-page {
	container: page / inline-size;
	min-height: 100%;
}

/* descendants, not children: a section page sits inside SectionShell's .kit-section-body */
.phosphor-page:has(.full-height) {
	height: 100%;
}

/* A page that wants calm behind it (the application builder) carries .page-solid: it fills the main column with the
   theme's page colour, so the moving atmosphere stays out of sight behind it however short the page is. */
.phosphor-page:has(> .kit-section-body > .page-solid) {
	display: flex;
	flex-direction: column;
}

.phosphor-page > .kit-section-body:has(> .page-solid) {
	flex: 1 0 auto;
	display: flex;
	flex-direction: column;
}

.page-solid {
	flex: 1 0 auto;
	background: var(--bg);
}

/* ── Rail (D1 §3): 72px, one 44×44 icon per section, radius 14; the current one filled
   --surface-2 with an accent icon. Desktop only: touch chrome keeps the drawer and tab bar. ── */
.phosphor-rail {
	flex-shrink: 0;
	width: var(--rail-w);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-5) 0;
	box-sizing: border-box;
	background: var(--rail-bg);
	border-right: var(--rail-edge, 1px solid var(--border-soft));
	overflow-y: auto;
	scrollbar-width: none;
}

.phosphor-rail-logo {
	width: 44px;
	height: 44px;
	flex-shrink: 0;
	display: grid;
	place-items: center;
	border-radius: 14px;
	border: 1px solid var(--border);
	background: var(--surface);
}

/* The logo is the Home link: on the home page it takes the current-section fill. */
.phosphor-rail-logo:hover,
.phosphor-rail-logo--on {
	background: var(--surface-2);
}

.phosphor-rail-logo:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

.phosphor-rail-sep {
	width: 28px;
	height: 1px;
	margin: 6px 0;
	flex-shrink: 0;
	background: color-mix(in srgb, var(--rail-ink) 45%, transparent);
}

/* The game's navigation groups (PortalNavigation): a list of groups, an inline group a labelled list of its
   destinations. A divider sits between groups. */
.phosphor-rail-groups,
.phosphor-rail-group-items {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-3);
}

/* The groups take the rail's room between the logo and its tools and scroll when they need more, as Discord's
   server list does; the logo and the tools stay put. The scrollbar is a thin one shown on hover, its gutter
   kept on both sides so the buttons stay centred. The padding keeps a focus ring in. */
.phosphor-rail-groups {
	flex: 1 1 0;
	min-height: 0;
	align-self: stretch;
	padding: var(--space-2) 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
	scrollbar-color: transparent transparent;
	scrollbar-gutter: stable both-edges;
}

.phosphor-rail-groups:hover,
.phosphor-rail-groups:focus-within {
	scrollbar-color: color-mix(in srgb, var(--rail-ink) 45%, transparent) transparent;
}

/* An edge with more beyond it fades out, so the rail says it scrolls: the top once the list has moved, the
   bottom until it reaches the end. A scroll-driven animation sets the two fades; a list that does not scroll
   has no scroll timeline, and keeps both at nothing. */
@property --rail-fade-top {
	syntax: "<length>";
	inherits: false;
	initial-value: 0px;
}

@property --rail-fade-bottom {
	syntax: "<length>";
	inherits: false;
	initial-value: 0px;
}

@keyframes phosphor-rail-fade {
	0% { --rail-fade-top: 0px; --rail-fade-bottom: 20px; }
	8%, 92% { --rail-fade-top: 20px; --rail-fade-bottom: 20px; }
	100% { --rail-fade-top: 20px; --rail-fade-bottom: 0px; }
}

@supports (animation-timeline: scroll()) {
	.phosphor-rail-groups {
		mask-image: linear-gradient(transparent, #000 var(--rail-fade-top), #000 calc(100% - var(--rail-fade-bottom)), transparent);
		animation: phosphor-rail-fade linear both;
		animation-timeline: scroll(self);
	}
}

.phosphor-rail-group {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-2);
}

/* A pop-out's button with no icon of its own: its first four destinations' icons, two by two. */
.phosphor-rail-flyout-grid {
	display: grid;
	grid-template-columns: repeat(2, 14px);
	grid-auto-rows: 14px;
	gap: var(--space-2);
}

.phosphor-rail-flyout-grid .mud-icon-root {
	width: 14px;
	height: 14px;
	font-size: var(--text-base);
}

/* The pop-out's list, beside the rail. */
.phosphor-rail-flyout-panel {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	min-width: 12rem;
	max-height: 70vh;
	overflow-y: auto;
	padding: var(--space-3);
}

.phosphor-rail-flyout-title {
	padding: var(--space-3) var(--space-4) var(--space-2);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-faint);
}

.phosphor-rail-flyout-items {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
}

.phosphor-rail-flyout-item {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	min-height: 2.25rem;
	padding: 0 var(--space-4);
	border-radius: var(--radius);
	color: var(--text);
	font-size: var(--text-base);
	text-decoration: none;
	transition: background var(--motion-fade), color var(--motion-fade);
}

.phosphor-rail-flyout-item:hover,
.phosphor-rail-flyout-item:focus-visible,
.phosphor-rail-flyout-item--on {
	background: var(--surface-3);
}

.phosphor-rail-flyout-item:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

.phosphor-rail-flyout-item--on {
	color: var(--accent);
}

.phosphor-rail-group + .phosphor-rail-group {
	padding-top: var(--space-3);
	border-top: 1px solid color-mix(in srgb, var(--rail-ink) 30%, transparent);
}

.phosphor-rail-group-label {
	max-width: calc(var(--rail-w) - 8px);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--text-xs);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--rail-ink);
}

.phosphor-rail-item {
	position: relative;
	width: 44px;
	height: 44px;
	flex-shrink: 0;
	display: grid;
	place-items: center;
	padding: 0;
	border: 0;
	border-radius: 14px;
	background: transparent;
	color: var(--rail-ink);
	cursor: pointer;
	text-decoration: none;
	transition: background var(--motion-fade), color var(--motion-fade);
}

.phosphor-rail-item:hover {
	background: var(--surface-2);
	color: var(--text);
}

.phosphor-rail-item:focus-visible {
	outline: 2px solid var(--rail-accent, var(--accent));
	outline-offset: 2px;
}

.phosphor-rail-item--on,
.phosphor-rail-item--on:hover {
	background: var(--surface-2);
	color: var(--accent);
}

.phosphor-rail-dot {
	position: absolute;
	top: 9px;
	right: 9px;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--rail-ink);
}

.phosphor-rail-dot--unread {
	background: var(--rail-accent, var(--accent));
	box-shadow: 0 0 6px rgba(var(--glow), 0.7);
}


.phosphor-rail-lang {
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	color: var(--rail-ink);
}

/* the account in the rail is the avatar alone */
.phosphor-profile-wrap--compact {
	padding: var(--space-2) 0 0;
	border-top: 0;
}

.phosphor-profile-wrap--compact .phosphor-profile-card {
	width: 44px;
	height: 44px;
	padding: 0;
	justify-content: center;
	background: transparent;
	border: 0;
	border-radius: 14px;
}

.phosphor-profile-wrap--compact .phosphor-profile-card:hover,
.phosphor-profile-wrap--compact .phosphor-profile-card:focus-visible {
	background: var(--surface-2);
}

.phosphor-profile-wrap--compact .phosphor-avatar-wrap,
.phosphor-profile-wrap--compact .phosphor-avatar {
	width: 36px;
	height: 36px;
	border-radius: 50%;
}

/* ── Page-sidebar slot (D1 §3): a section's SectionShell fills it; empty, the column is gone ── */
.phosphor-pagebar {
	position: relative;
	z-index: 1;
	flex-shrink: 0;
	width: var(--side-w);
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	/* Lets the page's texture (ThemeTexture) through faintly, so it runs on from the page without a seam. A theme
	   can make it glass: --pagebar-bg a thinner surface, --pagebar-glass the backdrop-filter behind it. */
	background: var(--pagebar-bg, color-mix(in srgb, var(--surface-3) 84%, transparent));
	backdrop-filter: var(--pagebar-glass, none);
	border-right: 1px solid var(--border-soft);
	transition: width var(--motion-move);
}

.phosphor-pagebar:not(:has(.kit-pagebar)) {
	display: none;
}

.phosphor-pagebar:has(.kit-pagebar--collapsed) {
	width: 62px;
}

/* ── Widget aside columns (left / right sidebar zones) ── */
.phosphor-widget-aside {
	flex-shrink: 0;
	border-right: 1px solid var(--border-soft);
	background: var(--surface);
	overflow-y: auto;
}
.phosphor-widget-aside--right {
	border-right: none;
	border-left: 1px solid var(--border-soft);
}

/* ── Footer widget zone ── */
.phosphor-footer {
	flex-shrink: 0;
	border-top: 1px solid var(--border-soft);
	background: var(--surface);
	padding: var(--space-3) var(--space-5);
}

/* ── Game-updated banner (GameUpdateBanner): above the app bar and drawers (MudBlazor's 1300),
   below its dialogs (1400) and snackbars ── */
.game-update-banner {
	position: fixed;
	top: 12px;
	left: 50%;
	transform: translateX(-50%);
	z-index: 1350;
	width: max-content;
	max-width: calc(100vw - 32px);
	box-shadow: var(--shadow-pop);
}

.game-update-banner .mud-alert-message {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3) var(--space-5);
}

/* ═══════════════════════════════════════════════════════════════════════
   Phosphor Refined sidebar (NavMenu) — full-height, logo · groups · profile.
   Mirrors prototype components.jsx Sidebar.
   ═══════════════════════════════════════════════════════════════════════ */
.phosphor-sidebar {
	width: 232px;
	flex-shrink: 0;
	height: 100%;
	display: none; /* touch chrome only: a desktop has the rail */
	flex-direction: column;
	overflow: hidden;
	background: var(--surface-3);
	border-right: 1px solid var(--border-soft);
}
.phosphor-sidebar--collapsed {
	width: 62px;
}

/* ── Logo / brand ── */
.phosphor-logo {
	height: 60px;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: var(--space-4);
	padding: 0 var(--space-5);
	cursor: pointer;
	text-decoration: none;
}
.phosphor-sidebar--collapsed .phosphor-logo {
	padding: 0;
	justify-content: center;
}
.phosphor-logo-mark {
	display: block;
	flex-shrink: 0;
	filter: drop-shadow(0 0 6px rgba(var(--glow), 0.35));
}
.phosphor-brand {
	font-family: var(--font-display);
	font-weight: 700;
	font-size: var(--text-base);
	letter-spacing: 0.02em;
	color: var(--text);
	white-space: nowrap;
}
.phosphor-brand-tag {
	font-family: var(--font-mono);
	font-size: var(--text-xs);
	color: var(--text-faint);
	letter-spacing: 0.08em;
	white-space: nowrap;
}

/* ── Nav scroll area + groups ── */
.phosphor-nav {
	flex: 1;
	overflow-y: auto;
	padding: var(--space-3) var(--space-4);
}
.nav-group {
	margin-bottom: 0.875rem;
}
/* A section that turned out to have no links is not a section. Every child of the Build group is
   permission- or data-gated, so an anonymous visitor and a registered account with no characters
   both saw a "BUILD" heading with nothing under it; Manage collapses the same way once its
   admin-only entries are gated out. Emptiness cannot be known statically — the children are
   <AuthorizeView> blocks and data-driven <ApplicationNavLinks>, and both render zero elements
   rather than an empty container — so this keys on the rendered result instead. Every nav entry,
   hardcoded or data-driven, is a .phosphor-nav-link, which is what makes the test reliable. */
.nav-group:not(:has(.phosphor-nav-link)) {
	display: none;
}
.nav-group-label {
	font-family: var(--font-kicker);
	font-size: var(--text-xs);
	letter-spacing: var(--kicker-tracking);
	text-transform: uppercase;
	color: var(--text-faint);
	padding: var(--space-2) var(--space-4) var(--space-3);
	pointer-events: none;
	user-select: none;
}
.phosphor-sidebar--collapsed .nav-group-label {
	display: none;
}
/* A pop-out group in the drawer: its name is the button that shows or hides its links. */
.nav-group-toggle {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	min-height: 2.75rem;
	padding: 0 var(--space-4) 0 0;
	border: 0;
	border-radius: var(--radius);
	background: none;
	color: var(--text-faint);
	font: inherit;
	cursor: pointer;
	text-align: left;
}
.nav-group-toggle .nav-group-label {
	padding-bottom: var(--space-2);
}
.nav-group-toggle:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}
.nav-group-chevron {
	font-size: var(--text-md);
	transition: transform var(--motion-spring);
}
.nav-group-toggle[aria-expanded="true"] .nav-group-chevron {
	transform: rotate(180deg);
}

/* ── Nav links (shared with ApplicationNavLinks) ── */
.phosphor-nav-link {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	padding: var(--space-3) var(--space-4);
	margin-bottom: 0.125rem;
	border-radius: var(--radius);
	color: var(--text-dim);
	font-family: var(--font-ui);
	font-size: var(--text-sm);
	font-weight: 500;
	letter-spacing: var(--nav-letterspacing);
	text-decoration: none;
	cursor: pointer;
	position: relative;
	white-space: nowrap;
	transition: background var(--motion-fade), color var(--motion-fade);
	animation: var(--nav-enter);
}

/* Play-session status dot: present while the play terminal is active, brighter/pulsing when there is
   unread activity (output arrived while not viewing /play). */
.phosphor-nav-dot {
	position: absolute;
	top: 8px;
	right: 9px;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--accent);
	box-shadow: 0 0 4px rgba(var(--glow), 0.45);
}
.phosphor-nav-dot--unread {
	box-shadow: 0 0 9px 2px rgba(var(--glow), 0.95);
	animation: phosphor-nav-pulse 1.4s ease-in-out infinite;
}
@keyframes phosphor-nav-pulse {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.5; }
}
/* A pulse that never stops is motion the visitor asked not to have (WCAG 2.2.2); the glow still says unread. */
@media (prefers-reduced-motion: reduce) {
	.phosphor-nav-dot--unread {
		animation: none;
	}
}
:root[data-reduce-motion="on"] .phosphor-nav-dot--unread {
	animation: none;
}

.phosphor-nav-link:hover {
	background: var(--surface-2);
	color: var(--text);
}
/* The current page: how it is marked is the theme's selection part (data-selection, css/themes/menus.css). Each
   --select-* variable falls back to this menu's own glowing bar, so a standard theme leaves it as it was. The mark is
   drawn under the text, inside the link's own stacking context. */
.phosphor-nav-link.active {
	isolation: isolate;
	background: var(--select-fill, rgba(var(--glow), 0.10));
	color: var(--select-ink, var(--accent));
	font-weight: 600;
	border-radius: var(--select-radius, var(--radius));
	box-shadow: var(--select-shadow, none);
	transform: var(--select-lift, none);
	text-shadow: var(--select-text-glow, none);
	animation: var(--nav-enter), var(--select-motion);
	animation-play-state: running, var(--ambient-play);
}
/* The play-status dot takes the line's dimmer ink where the selection fills it with the accent. */
.phosphor-nav-link.active .phosphor-nav-dot {
	background: var(--select-ink-dim, var(--accent));
}
.phosphor-nav-link.active::before {
	content: '';
	position: absolute;
	z-index: -1;
	inset: var(--select-mark-inset, 8px auto 8px 0);
	width: var(--select-mark-width, 3px);
	border-radius: var(--select-mark-radius, 2px);
	background: var(--select-mark, var(--accent));
	box-shadow: var(--select-mark-glow, 0 0 8px rgba(var(--glow), 0.7));
	transform: var(--select-mark-transform, none);
}
/* MudBlazor's filled and outlined text buttons in the theme's cut (data-buttons, css/themes/menus.css). Each
   --button-* variable falls back to MudBlazor's own value for that state, so a theme that leaves buttons standard
   keeps them as they were. Round icon buttons and a button group's squared inner corners are MudBlazor's alone, and
   a disabled or DisableElevation button keeps no shadow. */
.mud-button-root:is(.mud-button-filled, .mud-button-outlined):not(.mud-icon-button) {
	clip-path: var(--button-clip, none);
	transform: var(--button-transform, none);
	font-weight: var(--button-weight, var(--mud-typography-button-weight));
	letter-spacing: var(--button-tracking, var(--mud-typography-button-letterspacing));
	text-transform: var(--button-case, var(--mud-typography-button-text-transform));
}
.mud-button-root:is(.mud-button-filled, .mud-button-outlined):not(.mud-icon-button, .mud-button-group-root > *) {
	border-radius: var(--button-radius, var(--mud-default-borderradius));
}
.mud-button-root:is(.mud-button-filled, .mud-button-outlined):not(.mud-icon-button):active {
	transform: var(--button-press, var(--button-transform, none));
}
.mud-button-root.mud-button-filled:not(.mud-icon-button, .mud-button-disable-elevation, :disabled) {
	box-shadow: var(--button-shadow, 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12));
}
.mud-button-root.mud-button-filled:not(.mud-icon-button, .mud-button-disable-elevation, :disabled):is(:hover, :focus-visible) {
	box-shadow: var(--button-shadow, 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12));
}
.mud-button-root.mud-button-filled:not(.mud-icon-button, .mud-button-disable-elevation, :disabled):active {
	box-shadow: var(--button-press-shadow, var(--button-shadow, 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12)));
}
.mud-button-root.mud-button-outlined:not(.mud-icon-button) {
	box-shadow: var(--button-plain-shadow, none);
}
/* A cut that clips the button (notch) would clip a focus ring drawn outside it too, so the ring moves inside. */
.mud-button-root:is(.mud-button-filled, .mud-button-outlined):not(.mud-icon-button):focus-visible {
	outline-offset: var(--button-focus-offset, 2px);
}
.phosphor-nav-icon {
	font-size: var(--text-md);
	width: 18px;
	height: 18px;
	flex-shrink: 0;
	color: inherit;
}
.phosphor-nav-text {
	flex: 1;
	text-align: left;
	overflow: hidden;
	text-overflow: ellipsis;
}
.phosphor-sidebar--collapsed .phosphor-nav-link {
	justify-content: center;
	padding: var(--space-4) 0;
}

/* ── Bottom profile card ── */
.phosphor-profile-wrap {
	padding: var(--space-4);
	border-top: 1px solid var(--border-soft);
	flex-shrink: 0;
	position: relative;
}
.phosphor-profile-card {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	width: 100%;
	padding: var(--space-3) var(--space-3);
	background: var(--surface-2);
	border: 1px solid var(--border-soft);
	border-radius: var(--radius);
	cursor: pointer;
	text-decoration: none;
	transition: border-color var(--motion-fade);
}
.phosphor-profile-card:hover {
	border-color: rgba(var(--glow), 0.4);
}
.phosphor-sidebar--collapsed .phosphor-profile-card {
	justify-content: center;
	padding: var(--space-3) 0;
}
.phosphor-avatar-wrap {
	position: relative;
	width: 28px;
	height: 28px;
	flex-shrink: 0;
}
.phosphor-avatar {
	width: 28px;
	height: 28px;
	border-radius: var(--radius);
	display: grid;
	place-items: center;
	font-size: var(--text-xs);
	font-weight: 600;
	font-family: var(--font-ui);
	letter-spacing: 0.02em;
	border: 1px solid var(--border);
}
.phosphor-avatar-dot {
	position: absolute;
	right: -2px;
	bottom: -2px;
	width: 10px;
	height: 10px;
	border-radius: 99px;
	border: 2px solid var(--surface-2);
}
.phosphor-avatar-dot--online {
	background: var(--accent);
	box-shadow: 0 0 6px rgba(var(--glow), 0.7);
}
.phosphor-avatar-dot--offline {
	background: var(--text-faint);
}
.phosphor-profile-name {
	font-family: var(--font-ui);
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--text);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.phosphor-profile-sub {
	font-family: var(--font-mono);
	font-size: var(--text-xs);
	color: var(--text-faint);
	white-space: nowrap;
}
.phosphor-profile-chevron {
	font-size: var(--text-md);
	flex-shrink: 0;
	color: var(--text-faint);
	transition: transform var(--motion-spring), color var(--motion-fade);
}
.phosphor-profile-card[aria-expanded="true"] .phosphor-profile-chevron {
	color: var(--accent);
	transform: rotate(180deg);
}

/* ── Terminal ── */
.sharp-terminal-container {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow: hidden;
}

/* Fixed-grid font rules: disable ligatures/kerning so column widths are stable for NAWS grid measurement */
.sharp-terminal-output,
.term-input {
    font-family: var(--font-mono);
    font-variant-ligatures: none;
    font-feature-settings: "liga" 0, "calt" 0;
    font-kerning: none;
    letter-spacing: 0;
}

/* Stable scrollbar gutter prevents layout shift when content overflows */
.sharp-terminal-output {
    scrollbar-gutter: stable;
}

.terminal-line {
    line-height: 1.6;
    padding: var(--space-1) 0;
}

/* A terminal renders its lines in chunks of a hundred (TerminalChunkView). The browser skips laying out and
   painting a chunk off screen; it stays in the page for find, selection and a screen reader. The size is a
   stand-in until the chunk is first laid out, then its real height ("auto") is remembered. */
.sharp-terminal-chunk {
    content-visibility: auto;
    contain-intrinsic-size: auto 2400px;
}

/* A layout block in the terminal's pre-wrap text. As a block it already ends its line, so the newline
   softcode writes after a box() (before its footer) left an empty line under it. Laid inline across the
   whole line, the box is still on a line of its own and that newline only ends it. */
.sharp-terminal-output .ms-layout {
    display: inline-block;
    width: 100%;
    vertical-align: top;
}

/* Sent (echoed) commands — dim cyan, prefixed with an accent caret in markup */
.terminal-sent {
    color: var(--syntax-dbref);
    opacity: 0.85;
}

/* System/diagnostic lines — accent-dim, matches the prototype terminal */
.terminal-system {
    color: var(--accent-dim);
}

/* ── Terminal input bar (prototype MushTerminal form) ── */
.sharp-terminal-connbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border-top: 1px solid var(--border-soft);
}

.sharp-terminal-input {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    background: var(--surface);
    border-top: 1px solid var(--border-soft);
    /* Flush with the bottom of a rounded card (Play's): the focus ring below bends with the card's corners
       instead of being cut off square by them. */
    border-radius: 0 0 var(--scene-card-inner-radius, 0) var(--scene-card-inner-radius, 0);
}

.term-prompt {
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: var(--text-base);
    font-weight: 700;
    flex-shrink: 0;
    line-height: 1;
}

.term-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    outline: none;
    color: var(--text);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    caret-color: var(--accent);
}

.term-input::placeholder {
    color: var(--text-faint);
}

.term-input:disabled {
    opacity: 0.5;
}

.term-send,
.term-clear {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border-radius: var(--radius);
    cursor: pointer;
    transition: background var(--motion-fade);
}

.term-send {
    background: rgba(var(--glow), 0.12);
    border: 1px solid rgba(var(--glow), 0.25);
    color: var(--accent);
}

.term-send:hover:not(:disabled) {
    background: rgba(var(--glow), 0.2);
}

.term-send:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.term-clear {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-faint);
}

.term-clear:hover {
    color: var(--text);
    border-color: var(--border);
    background: var(--surface-2);
}

/* ── MarkupString (MString) ──
   Colors arrive as inline `style` from the MarkupString.Html emitter; the ms-* classes carry the
   rest. The packages' own rules for them (AnsiCss.Fixed, LayoutCss.Fixed) are data/markup.css,
   generated at build by tools/ClientData and imported ahead of this file (custom.css). What
   follows here is portal-only and builds on them. */
.ms-underline.ms-strike { text-decoration: underline line-through; }
/* Reverse video for a run that has neither colour set: the package's .ms-invert falls back
   through --ms-fg/--ms-bg, so pointing those at the terminal pane's own colours themes the swap. */
:root { --ms-fg: var(--text); --ms-bg: var(--surface); }

/* align() and the other laid-out text arrive as <pre class="ms-preformatted">. In game output that text
   is part of a line, not a block: a block <pre> takes the browser's own margins and monospace size, so
   every table row sat between blank lines and in a smaller font than the rules drawn around it. */
pre.ms-preformatted {
    display: inline;
    margin: 0;
    font: inherit;
    white-space: pre-wrap;
}

/* Inline, each line's background covers only the glyphs, so a shaded block (a help example) showed the
   terminal's line spacing between its lines as stripes. Shaded text lays out its own lines, at the font's
   own height, and sits on the line it starts on; unshaded text keeps the line's flow, as a telnet client shows it. */
pre.ms-preformatted:has(> [style*="background-color"]) {
    display: inline-block;
    vertical-align: text-top;
    line-height: normal;
}

/* Portal-only: LayoutCss.Fixed leaves its title gap in a rule or divider with no title, which splits the line in two.
   With no title it also has no height, so its line sat at the top of its row instead of across the middle. */
.ms-rule:empty, .ms-divider:empty { gap: 0; min-height: 1lh; }
/* The terminal uses word-break: break-word for plain output. Reset it on tables: that
   legacy value makes intrinsic sizing treat every character as a break opportunity,
   even with overflow-wrap: break-word. Headers then shrink below whole words while
   a growing description keeps the spare width. Preserve emergency wrapping in cells. */
.ms-table { word-break: normal; overflow-wrap: break-word; }
.ms-table .ms-text { overflow-wrap: break-word; }

/* Command links (xch_cmd) — clickable, run a MUSH command. Distinct from URL links.
   The package's .ms-cmd-link sets the cursor and dotted underline; the portal colours it. */
.ms-cmd-link {
    color: var(--accent);
}
.ms-cmd-link:hover {
    text-decoration-style: solid;
}
/* Command links carry role="button" tabindex="0"; show a focus ring for keyboard users. */
.ms-cmd-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    text-decoration-style: solid;
}

/* tone(): text coloured by a theme colour's name, so it follows the reader's portal theme. The same names are
   @theme's colours on telnet (ToneMarkup). */
.tone-foreground { color: var(--text); }
.tone-strong { color: var(--text); font-weight: 700; }
.tone-primary { color: var(--accent); }
.tone-secondary { color: var(--special); }
.tone-tertiary { color: var(--tone-tertiary); }
.tone-muted { color: var(--text-dim); }
.tone-subtle { color: var(--text-faint); }
.tone-link { color: var(--accent); }
.tone-highlight { background-color: var(--highlight); color: var(--text); }
.tone-success { color: var(--success); }
.tone-warning { color: var(--warn); }
.tone-error { color: var(--danger); }
.tone-info { color: var(--info); }
.tone-red { color: var(--hue-red); }
.tone-orange { color: var(--hue-orange); }
.tone-yellow { color: var(--hue-yellow); }
.tone-green { color: var(--hue-green); }
.tone-cyan { color: var(--hue-cyan); }
.tone-blue { color: var(--hue-blue); }
.tone-purple { color: var(--hue-purple); }
.tone-pink { color: var(--hue-pink); }
/* A gradient stop that is a tone() name (MarkupString writes var(--ms-tone-<role>, <colour>)): the same colours. */
:is(.ms-shaded, .ms-gauge-bar) {
    --ms-tone-foreground: var(--text);
    --ms-tone-primary: var(--accent);
    --ms-tone-secondary: var(--special);
    --ms-tone-tertiary: var(--tone-tertiary);
    --ms-tone-muted: var(--text-dim);
    --ms-tone-subtle: var(--text-faint);
    --ms-tone-link: var(--accent);
    --ms-tone-highlight: var(--highlight);
    --ms-tone-success: var(--success);
    --ms-tone-warning: var(--warn);
    --ms-tone-error: var(--danger);
    --ms-tone-info: var(--info);
    --ms-tone-red: var(--hue-red);
    --ms-tone-orange: var(--hue-orange);
    --ms-tone-yellow: var(--hue-yellow);
    --ms-tone-green: var(--hue-green);
    --ms-tone-cyan: var(--hue-cyan);
    --ms-tone-blue: var(--hue-blue);
    --ms-tone-purple: var(--hue-purple);
    --ms-tone-pink: var(--hue-pink);
}

/* ── Softcode Editor ── */
.mush-editor-fill {
    width: 100%;
    height: 100%;
    background-color: var(--editor-bg);
}

.mush-editor-fill .monaco-editor-container {
    height: 100%;
}

/* ── Dark DataGrid (attribute list) ── */
.mush-dark-grid .mud-table-root,
.mush-dark-grid .mud-table-container,
.mush-dark-grid .mud-table,
.mush-dark-grid thead tr,
.mush-dark-grid tbody tr {
    background-color: transparent;
}
.mush-dark-grid thead tr th {
    background-color: var(--surface-3);
    border-bottom: 1px solid var(--border);
    color: var(--text-dim);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.mush-dark-grid tbody tr td {
    border-bottom: 1px solid var(--border-soft);
    color: var(--text);
}
.mush-dark-grid tbody tr:hover td {
    background-color: color-mix(in srgb, var(--text) 4%, transparent);
}
.mush-dark-grid .mud-table-pagination {
    background-color: var(--surface-3);
    border-top: 1px solid var(--border);
}

/* ── HelpDrawer markdown rendering ── */
.mush-help-md {
    font-family: Consolas, 'Courier New', monospace;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--text);
}
.mush-help-md p { margin: 0 0 0.5rem; }
.mush-help-md pre {
    background: var(--code-bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: var(--space-3) var(--space-4);
    overflow-x: auto;
    white-space: pre;
}
.mush-help-md code {
    background: color-mix(in srgb, var(--text) 7%, transparent);
    border-radius: 3px;
    padding: var(--space-1) var(--space-2);
    font-family: inherit;
    font-size: 0.92em;
    color: var(--syntax-string);
}
.mush-help-md pre code {
    background: none;
    padding: 0;
    color: var(--code-text);
}
.mush-help-md a[href^="help:"] {
    color: var(--syntax-link);
    text-decoration: none;
    cursor: pointer;
}
.mush-help-md a[href^="help:"]:hover {
    text-decoration: underline;
}
.mush-help-md h1, .mush-help-md h2, .mush-help-md h3 {
    color: var(--syntax-heading);
    margin: 0.75rem 0 0.375rem;
}
.mush-help-md ul, .mush-help-md ol {
    padding-left: var(--space-6);
    margin: 0 0 0.5rem;
}
.mush-help-md blockquote {
    border-left: 3px solid var(--border);
    padding-left: var(--space-4);
    color: var(--text-faint);
    margin: 0 0 0.5rem;
}
.mush-help-md strong { color: var(--syntax-emphasis); }

/* ── Wiki rendered content ── */
/* Applied to the MudCard wrapping rendered wiki HTML (WikiDisplay.razor).      */
/* Colors lean on MudBlazor palette variables so they follow the active theme. */

.WikiContent {
    line-height: 1.65;
}

/* Links inside wiki content — themed color, underline on hover */
.WikiContent .mud-card-content a {
    color: var(--accent);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color var(--motion-fade), color var(--motion-fade);
}

.WikiContent .mud-card-content a:hover,
.WikiContent .mud-card-content a:focus-visible {
    color: var(--accent-dim);
    border-bottom-color: currentColor;
}

/* External links (absolute URLs) get a subtle visual distinction */
.WikiContent .mud-card-content a[href^="http"] {
    font-style: italic;
}

/* D1 §4.8: a character link reads as a mention — body colour, a thin underline in the faint colour */
.WikiContent .mud-card-content a.mention {
    color: inherit;
    border-bottom: 0;
    text-decoration: underline 1px color-mix(in srgb, var(--name, var(--text-faint)) var(--mention-alpha), transparent);
    text-underline-offset: var(--mention-offset);
}

.WikiContent .mud-card-content a.mention:hover,
.WikiContent .mud-card-content a.mention:focus-visible {
    color: inherit;
    text-decoration-color: var(--name, var(--text-faint));
}

/* Redlinks — wiki links to pages that don't exist yet */
.WikiContent .mud-card-content a.wiki-redlink {
    color: var(--link-missing);
}

/* the dashed border is the redlink's underline; a mention already has one */
.WikiContent .mud-card-content a.wiki-redlink:not(.mention) {
    border-bottom: 1px dashed currentColor;
}

.WikiContent .mud-card-content a.wiki-redlink:hover {
    color: var(--link-missing);
}

/* Headings */
.WikiContent .mud-card-content h1,
.WikiContent .mud-card-content h2,
.WikiContent .mud-card-content h3,
.WikiContent .mud-card-content h4,
.WikiContent .mud-card-content h5,
.WikiContent .mud-card-content h6 {
    color: var(--text);
    margin: 1.2em 0 0.4em;
    line-height: 1.25;
}

.WikiContent .mud-card-content h1,
.WikiContent .mud-card-content h2 {
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.2em;
}

/* Paragraphs and lists */
.WikiContent .mud-card-content p {
    margin: 0 0 0.9em;
}

.WikiContent .mud-card-content ul,
.WikiContent .mud-card-content ol {
    padding-left: 1.6em;
    margin: 0 0 0.9em;
}

.WikiContent .mud-card-content li {
    margin: 0.2em 0;
}

/* Inline code and code blocks */
.WikiContent .mud-card-content code {
    background: color-mix(in srgb, var(--text) 7%, transparent);
    border-radius: 3px;
    padding: var(--space-1) var(--space-2);
    font-family: Consolas, 'Courier New', monospace;
    font-size: 0.9em;
}

.WikiContent .mud-card-content pre {
    background: var(--code-bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: var(--space-4) var(--space-5);
    overflow-x: auto;
    margin: 0 0 1em;
}

.WikiContent .mud-card-content pre code {
    background: none;
    padding: 0;
}

/* Blockquotes */
.WikiContent .mud-card-content blockquote {
    border-left: 3px solid var(--accent-dim);
    margin: 0 0 1em;
    padding: 0.2em 0 0.2em 1em;
    color: var(--text-dim);
}

/* Keep equipment/rules tables scrollable without widening the whole article. The focusable
   wrapper preserves native table semantics and supports keyboard horizontal scrolling. */
.WikiContent .wiki-table-scroll {
    max-width: 100%;
    overflow-x: auto;
    margin: 0 0 1em;
}

.WikiContent .wiki-table-scroll:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Tables (Markdig pipe tables) */
.WikiContent .mud-card-content table {
    border-collapse: collapse;
    margin: 0 0 1em;
    width: 100%;
}

.WikiContent .mud-card-content th,
.WikiContent .mud-card-content td {
    border: 1px solid var(--border);
    padding: var(--space-3) var(--space-4);
    text-align: left;
}

.WikiContent .mud-card-content th {
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

.WikiContent .mud-card-content tbody tr:hover {
    background: color-mix(in srgb, var(--text) 3%, transparent);
}

.WikiContent .wiki-table-scroll table {
    margin-bottom: 0;
}

/* Horizontal rules */
.WikiContent .mud-card-content hr {
    border: none;
    border-top: 1px solid var(--border);
    margin: 1.5em 0;
}

/* ── Hero mode (home page) ── */
/* ── Centred blocks (`::: center`) ── */
/* The author's way to centre part of a page; the terminal renderer centres the same block.
   Images and tables center by their margins; lists shrink-wrap and center as a unit, keeping
   bullets beside their (left-aligned) items instead of at the page edge. */
.WikiContent div.center {
    text-align: center;
}

.WikiContent div.center .wiki-img,
.WikiContent div.center table {
    margin-left: auto;
    margin-right: auto;
}

.WikiContent div.center ul,
.WikiContent div.center ol {
    display: inline-block;
    text-align: left;
}

/* ── Flex layouts (`flex` / `item`, MarkdownFlexExtension) ── */
/* Shared by wiki pages and help entries. Settings arrive as classes; an item's grow, basis and minimum
   width as custom properties holding validated numbers. The minimum is in character columns, the same
   number the terminal renderer stacks at, so a layout that stacks on a phone stacks on a narrow client. */
.md-flex {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-5);
    margin: 0 0 1em;
}

.md-flex.md-column { flex-direction: column; }
.md-flex.md-nowrap { flex-wrap: nowrap; }
/* N × 0.5rem; HtmlFlexItemRenderer.BasisCss takes the same sizes out of a row for a percentage basis. */
.md-flex.md-gap-0 { gap: 0; }
.md-flex.md-gap-1 { gap: var(--space-3); }
.md-flex.md-gap-2 { gap: var(--space-5); }
.md-flex.md-gap-3 { gap: var(--space-6); }
.md-flex.md-gap-4 { gap: var(--space-7); }
.md-flex.md-align-start { align-items: flex-start; }
.md-flex.md-align-center { align-items: center; }
.md-flex.md-align-end { align-items: flex-end; }
.md-flex.md-align-stretch { align-items: stretch; }
.md-flex.md-justify-start { justify-content: flex-start; }
.md-flex.md-justify-center { justify-content: center; }
.md-flex.md-justify-end { justify-content: flex-end; }
.md-flex.md-justify-between { justify-content: space-between; }

.md-item {
    flex: var(--md-grow, 1) 1 0%;
    min-width: min(100%, calc(var(--md-min, 24) * 1ch));
}

/* A fixed item may shrink when the row runs short (a percentage basis already has the gaps taken out). */
.md-item.md-fixed {
    flex: 0 1 var(--md-basis);
    max-width: 100%;
}

/* In a column the main axis is vertical, where a basis would set a height; it stays a width. */
.md-flex.md-column > .md-item { min-width: 0; }
.md-flex.md-column > .md-item.md-fixed {
    flex: none;
    width: var(--md-basis);
}
.md-item.md-self-start { align-self: flex-start; }
.md-item.md-self-center { align-self: center; }
.md-item.md-self-end { align-self: flex-end; }
.md-item.md-self-stretch { align-self: stretch; }
.md-item > :first-child { margin-top: 0; }
.md-item > :last-child { margin-bottom: 0; }

/* ── Wiki directive blocks (dynamic listings: category / tag / pagelist / recent / facts) ── */

.wiki-directive-block {
    border-left: 3px solid var(--accent-dim);
    background: color-mix(in srgb, var(--text) 3%, transparent);
    border-radius: 0 4px 4px 0;
    padding: 0.6em 1.1em;
    margin: 0 0 1em;
}

.wiki-directive-block .wiki-directive-header {
    color: var(--text);
    font-weight: 600;
    margin-bottom: 0.35em;
}

.wiki-directive-block .wiki-directive-empty,
.wiki-directive-block .wiki-directive-loading {
    color: var(--text-dim);
    margin: 0;
}

.WikiContent .mud-card-content .wiki-directive-list,
.wiki-directive-list {
    list-style: none;
    padding-left: 0.2em;
    margin: 0.3em 0 0;
}

.wiki-directive-list li {
    margin: 0.3em 0;
}

.wiki-directive-list .wiki-directive-date {
    color: var(--text-dim);
    font-size: 0.85em;
}

/* Pictures put in the game (avatars, portraits, galleries, room, thing and exit pictures, scene headers and sidebar
   rows, maps, wiki and schema images) take the theme's picture tone, as its banners do. A component marks such a picture game-picture;
   the wiki's Markdown marks its own wiki-img. */
.game-picture,
.wiki-img {
    filter: var(--image-filter);
}

/* A player who turns image filters off (ThemeService.ImageFiltersPart) sees every picture as it was made, under any
   theme. This layer comes after the themes layer's picture tones; a theme's own stylesheet is unlayered, so
   ResolvedTheme.Css repeats this rule after it. */
:root[data-image-filters="off"] {
    --image-filter: none;
}

/* Wiki images — lazy loading, constrained width */
.wiki-img {
    max-width: 100%;
    display: block;
    margin: 0.5rem 0;
}

/* Only auto-size images that don't carry an explicit height attribute
   (authors can size via ![alt](src){width=200 height=100}). A stored picture carries its own
   width and height (wiki-img-sized): those give the browser its shape before it loads, and it
   scales with the column like any other. */
.wiki-img:not([height]),
.wiki-img-sized {
    height: auto;
}

/* ═══════════════════════════════════════════════════════════════════════
   MOBILE TOOLKIT + RESPONSIVE SHELL
   Phone / tablet-landscape / desktop, split by width AND input capability.

   Responsive model (3 tiers, width × input capability):
     • PHONE content — `@media (max-width: 760px)`. Single-column stacking, condensed
       chrome. This is the breakpoint page authors use for content. In Razor <style>
       blocks write `@@media (max-width: 760px)` (Razor escapes `@`; a bare `@media`
       is parsed as a code transition and fails to compile).
     • TABLET / landscape phone — wider than 760px, so they KEEP the desktop
       multi-column content (more readable). Don't add a single-column rule for them.
     • TOUCH CHROME — `@media (max-width: 760px), (pointer: coarse)`. The shell nav
       (drawer + bottom bar vs. desktop sidebar) keys off this so any touch device,
       including a wide tablet or landscape phone, gets touch-friendly chrome while a
       mouse/trackpad desktop keeps the sidebar. Mirrored by sharpmushLayout.isTouchChrome().
     • TOUCH ergonomics — `@media (pointer: coarse)`: 44px min tap targets, no hover-only UI.
     • Full-height regions: size with `svh` and a `vh` fallback, e.g.
       "height: 100vh;" then "height: 100svh;" — the vh line is the fallback for
       browsers without svh; svh fits the SMALL viewport, so it never crops under
       the mobile address bar and never resizes on scroll.
       Default to `svh`, NOT `dvh`. `dvh` tracks the live viewport and recalculates
       every time the toolbar slides in/out (layout jank); reserve it for the rare
       element that must follow the visible viewport in real time.
     • Any flex/grid child holding wide content (terminal, tables, code, pre)
       needs `min-width: 0`; prefer `minmax(0, 1fr)` over a bare `1fr` track.
     • Min touch target 44px for interactive controls.
   Helper classes below are available to every page.
   ═══════════════════════════════════════════════════════════════════════ */

/* Backdrop behind the off-canvas nav drawer — inert until the shell is .nav-open. */
.phosphor-nav-backdrop {
	display: none;
}

/* A group's phone tab opens its destinations in a sheet over the tab bar (touch chrome only). */
.phosphor-tabsheet {
	display: none;
}
.phosphor-tabsheet-title {
	margin: 0 0 0.5rem;
	padding: 0 var(--space-3);
	font-family: var(--font-ui);
	font-size: var(--text-sm);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-faint);
}
.phosphor-tabsheet-items {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--space-1);
}
.phosphor-tabsheet-item {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	min-height: 2.75rem;
	padding: 0 var(--space-4);
	border-radius: 0.75rem;
	color: var(--text);
	text-decoration: none;
	font-family: var(--font-ui);
	transition: background var(--motion-fade), color var(--motion-fade);
}
.phosphor-tabsheet-item--on {
	background: var(--surface-2);
}
/* Only a real pointer hovers: a tap would leave the row it landed on lit. */
@media (hover: hover) {
	.phosphor-tabsheet-item:hover {
		background: var(--surface-2);
	}
}
.phosphor-tabsheet-item--on {
	color: var(--accent);
}
.phosphor-tabsheet-icon {
	font-size: var(--text-lg);
}

/* ── Hybrid bottom navigation (mobile only; hidden here, shown in the @media block).
   Primary destinations stay visible; the "Menu" tab opens the full grouped drawer. ── */
.phosphor-bottomnav {
	display: none;
}
.phosphor-bottomtab {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	background: transparent;
	border: none;
	cursor: pointer;
	text-decoration: none;
	color: var(--text-faint);
	font-family: var(--font-ui);
	transition: color var(--motion-fade);
}
.phosphor-bottomtab-icon {
	font-size: var(--text-xl);
}
.phosphor-bottomtab-label {
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--text-xs);
	letter-spacing: 0.02em;
}
.phosphor-bottomtab--on {
	color: var(--accent);
}

/* The section button in the touch header only where the page has a section sidebar to open. */
.phosphor-shell:not(:has(.kit-pagebar)) .phosphor-pagebar-btn {
	display: none;
}

/* Play's focus mode (.play--focus): the play area is the whole app. The shell's chrome steps away with
   the page sidebar — the rail, the touch header, the TopBar zone, the footer and the widget asides —
   and the scene card's focus button brings it back. The predicate is a child chain from the shell to
   the page root because game output (tagwrap, wshtml) renders as HTML inside the terminal and can carry
   any class name; a bare descendant match would let it switch the chrome off. */
.phosphor-shell:has(> .phosphor-content-col > .phosphor-main > .phosphor-page > .kit-section-body > .play--focus) :is(.phosphor-rail, .phosphor-topbar, .phosphor-topzone, .phosphor-footer, .phosphor-widget-aside) {
	display: none;
}

/* Play merges the shell's touch header into its card header (Play.razor): while the card is mounted the
   header (or its short-landscape rail) steps away, the avatar opens the drawer, and the drawer's tools row
   carries the header's other buttons. The roster and no-character states have no card and keep the header.
   The same child chain as focus mode, so game output cannot match it. */
.phosphor-drawer-tools {
	display: none;
}
.phosphor-shell:has(> .phosphor-content-col > .phosphor-main > .phosphor-page > .kit-section-body > .play) .phosphor-topbar {
	display: none;
}
.phosphor-shell:has(> .phosphor-content-col > .phosphor-main > .phosphor-page > .kit-section-body > .play) .phosphor-drawer-tools {
	display: flex;
	align-items: center;
	justify-content: space-around;
	flex-shrink: 0;
	padding: var(--space-3) var(--space-4);
	border-top: 1px solid var(--border-soft);
}

/* ══ PHONE-WIDTH content: single-column helpers + a condensed topbar. Tablets and
   landscape phones are WIDER than this, so they keep the desktop multi-column content
   (the readability win) — only their chrome goes touch-friendly (next block). ══ */
@media (max-width: 760px) {

	/* A condensed header: the search is an icon, and the language stays (a phone has no other). */
	.phosphor-topbar { gap: var(--space-3); padding: 0 var(--space-4); }
}

/* ══ TOUCH CHROME: the nav becomes an off-canvas drawer + a bottom tab bar. Applies to
   any touch device (pointer: coarse) OR any narrow window — so phones, tablets, and
   landscape phones all get the touch-friendly chrome instead of the desktop sidebar,
   while a mouse/trackpad desktop (pointer: fine + wide) keeps the sidebar. Keep this
   media condition in sync with sharpmushLayout.touchChromeQuery in layout.js. ══ */
@media (max-width: 760px), (pointer: coarse) {
	/* The rail is desktop chrome; touch chrome has the drawer, the header and the bottom tab bar.
	   Anything that is not touch chrome (a fine pointer, or none at all) gets the rail alone. */
	.phosphor-rail { display: none; }
	.phosphor-sidebar { display: flex; }
	.phosphor-topbar { display: flex; }
	.phosphor-main { border-block-width: 0; }
	.phosphor-shell::after { display: none; }

	/* The section panel is always the full sidebar here (SectionShell ignores a desktop collapse). */
	.phosphor-shell .phosphor-pagebar .kit-pagebar-toggle { display: none; }

	/* The sidebar leaves the flex flow and slides in over the content, full-width. */
	.phosphor-sidebar {
		position: fixed;
		top: 0;
		left: 0;
		bottom: 0;
		width: 248px;
		z-index: 1001;
		transform: translateX(-100%);
		/* Hidden once it has slid out, so a closed drawer's links are neither tabbed to nor read out
		   off-screen; visibility switches after the slide closing and at once opening. */
		visibility: hidden;
		transition: transform var(--motion-exit), visibility 0s var(--duration-exit);
		box-shadow: var(--shadow-pop);
	}
	.phosphor-shell--nav-open .phosphor-sidebar {
		transform: translateX(0);
		visibility: visible;
		transition: transform var(--motion-enter), visibility 0s;
	}
	/* Keep brand/labels/text visible even if the desktop rail was collapsed. */
	.phosphor-sidebar--collapsed { width: 248px; }

	/* Dimmed backdrop, shown only while the drawer is open. */
	.phosphor-nav-backdrop {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 1000;
		background: var(--overlay);
		opacity: 0;
		pointer-events: none;
		transition: opacity var(--motion-fade);
	}
	.phosphor-shell--nav-open .phosphor-nav-backdrop {
		opacity: 1;
		pointer-events: auto;
	}

	/* The page sidebar leaves the flow too: an off-canvas panel opened from the header (below). */
	.phosphor-pagebar {
		position: fixed;
		top: 0;
		left: 0;
		bottom: 0;
		width: min(280px, 86vw);
		z-index: 1001;
		/* A drawer over the page: fully opaque, so the page does not show through its links. */
		background: var(--surface-3);
		transform: translateX(-100%);
		visibility: hidden;
		transition: transform var(--motion-exit), visibility 0s var(--duration-exit);
		box-shadow: var(--shadow-pop);
	}
	.phosphor-pagebar:has(.kit-pagebar--collapsed) { width: min(280px, 86vw); }
	.phosphor-shell--pagebar-open .phosphor-pagebar {
		transform: translateX(0);
		visibility: visible;
		transition: transform var(--motion-enter), visibility 0s;
	}
	.phosphor-shell--pagebar-open .phosphor-nav-backdrop {
		opacity: 1;
		pointer-events: auto;
	}

	/* A group tab's sheet rises from the bottom edge over the tab bar, under the backdrop's dim. */
	.phosphor-tabsheet {
		display: block;
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 1001;
		max-height: 70svh;
		overflow-y: auto;
		padding: var(--space-5) var(--space-4) calc(var(--space-5) + env(safe-area-inset-bottom));
		background: var(--surface-3);
		border-top: 1px solid var(--border-soft);
		border-radius: 1rem 1rem 0 0;
		box-shadow: var(--shadow-pop);
		transform: translateY(100%);
		visibility: hidden;
		transition: transform var(--motion-exit), visibility 0s var(--duration-exit);
	}
	.phosphor-shell--sheet-open .phosphor-tabsheet {
		transform: translateY(0);
		visibility: visible;
		transition: transform var(--motion-enter), visibility 0s;
	}
	.phosphor-shell--sheet-open .phosphor-nav-backdrop {
		opacity: 1;
		pointer-events: auto;
	}

	/* Content column owns the full width now the sidebar is out of flow. */
	.phosphor-content-col { width: 100%; }

	/* Global widget asides step aside so content keeps the full width. */
	.phosphor-widget-aside { display: none; }

	/* ── Hybrid bottom navigation bar ── */
	.phosphor-bottomnav {
		display: flex;
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 40;
		height: calc(3.625rem + env(safe-area-inset-bottom));
		padding-bottom: env(safe-area-inset-bottom);
		background: color-mix(in srgb, var(--surface-3) 92%, transparent);
		backdrop-filter: blur(10px);
		-webkit-backdrop-filter: blur(10px);
		border-top: 1px solid var(--border-soft);
	}
	/* Keep page content clear of the fixed bar, via --bottomnav-clearance (tokens.css) so a
	   page's own sticky-bottom chrome can read the same offset without a media query. */
	.phosphor-shell--bottomnav {
		--bottomnav-clearance: calc(3.625rem + env(safe-area-inset-bottom));
	}
	.phosphor-shell--bottomnav .phosphor-main {
		padding-bottom: var(--bottomnav-clearance);
	}
}

/* ══ SHORT LANDSCAPE: a phone held sideways is short of height, not width. The touch header turns into a
   rail down the left edge — the same buttons, stacked — so the page keeps the full height. Touch chrome
   only (the condition above, ANDed with the shape); a mouse desktop already has the rail. An upright phone
   keeps the header on top: it is short of width, not height. The shape is sharpmushLayout.shortScreenQuery
   in layout.js (the landscape half of its compactScreenQuery), which Play follows as play--short. ══ */
@media (max-width: 760px) and (orientation: landscape) and (max-height: 32rem),
	(pointer: coarse) and (orientation: landscape) and (max-height: 32rem) {
	.phosphor-topbar {
		position: fixed;
		top: 0;
		bottom: 0;
		left: 0;
		width: calc(var(--touch-rail-w) + env(safe-area-inset-left));
		height: auto;
		flex-direction: column;
		gap: var(--space-2);
		padding: max(var(--space-3), env(safe-area-inset-top)) 0 max(var(--space-3), env(safe-area-inset-bottom)) env(safe-area-inset-left);
		border-bottom: 0;
		border-right: 1px solid var(--border-soft);
	}
	/* A column of icons: the logo stays, the name has no room. The link still takes the spare height, which
	   keeps search, language and terminal at the foot of the column. */
	.phosphor-topbar-game {
		flex-direction: column;
		padding-top: var(--space-3);
	}
	.phosphor-topbar-name { display: none; }
	.phosphor-shell {
		--touch-rail-w: 56px;
	}
	.phosphor-content-col {
		box-sizing: border-box;
		padding-left: calc(var(--touch-rail-w) + env(safe-area-inset-left));
	}
	/* Play merges the rail into its card header (above); the page gets its width back. */
	.phosphor-shell:has(> .phosphor-content-col > .phosphor-main > .phosphor-page > .kit-section-body > .play) .phosphor-content-col {
		padding-left: 0;
	}
}

/* ══ Touch ergonomics: 44px tap targets on any touch device, regardless of width. ══ */
@media (pointer: coarse) {
	.phosphor-nav-link { min-height: 44px; }
	.phosphor-icon-btn { min-width: 44px; min-height: 44px; }
	/* The D1 kit (Components/Kit): rows, capsules and chips are 30–40px on a fine pointer. */
	.kit-row, .kit-capsule, .kit-chip { min-height: 44px; }
	.kit-capsule--icon { min-width: 44px; }
	.kit-portrait, .kit-tile { min-height: 44px; }
	/* The wiki sidebar's members button and the help sidebar's symbol chips (scoped CSS takes no media query). */
	.wiki-side-members { width: 44px; height: 44px; }
	.help-side-symbol { display: grid; place-items: center; min-width: 44px; min-height: 44px; }
}

/* Wiki categories: a page's category bar (MediaWiki's catlinks) and a category page's subcategories.
   Global rather than scoped because WikiDisplay and WikiCategoryMembers both render the links. */
.wiki-catlinks {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: 12px;
}

.wiki-catlinks-label {
    color: var(--text-faint);
    font-size: var(--text-sm);
}

.wiki-catlink {
    height: 26px;
    padding: 0 var(--space-4);
    border-radius: 13px;
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--text-sm);
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.wiki-catlink:hover,
.wiki-catlink:focus-visible {
    background: var(--border);
    outline: none;
}

/* ── Skip links (WCAG 2.4.1, bypass blocks) ──
   The first stops in the tab order: past the rail and the section menu to the page, or straight to the
   page's main text box when it has one (input-focus.js). Off screen until focused. */
.skip-links {
	position: fixed;
	top: 0.5rem;
	left: 0.5rem;
	z-index: 2000;
	display: flex;
	gap: var(--space-3);
}
.skip-link {
	position: absolute;
	left: -10000px;
	padding: var(--space-3) var(--space-5);
	border-radius: var(--radius);
	background: var(--accent);
	color: var(--accent-on);
	font-weight: 600;
	text-decoration: none;
}
.skip-link:focus-visible,
.skip-link:focus {
	position: static;
}
:root:not(:has([data-primary-input])) .skip-link--input {
	display: none;
}
/* The skip link's target is focused only to move the reading point; it is not a control. */
.phosphor-main:focus {
	outline: none;
}

/* The main text boxes draw no outline of their own (their row is the box), so the row shows where focus is:
   WCAG 2.4.7. Unscoped, because the field and its row come from different components. */
.kit-bottom-bar:has(.composer-input:focus-visible, .comm-input:focus-visible) {
	border-color: var(--accent);
	box-shadow: 0 0 0 1px var(--accent);
}

.sharp-terminal-input:has(.term-input:focus-visible),
.kit-palette-field:has(input:focus-visible) {
	box-shadow: inset 0 0 0 2px var(--accent);
}

/* The live scene page while the on-screen keyboard is up (layout.js watchSceneCompose): pinned to the part of the
   screen the keyboard leaves, so the header stays at the top, the compose sheet sits on the keyboard, and the browser
   has no reason to scroll the page up to the field and back down again when the keyboard closes. Above the bottom
   navigation (40), which the keyboard covers anyway. The height is in
   SceneLive.razor.css, where it can outweigh the page's own. */
.scene-live-page--keyboard {
	position: fixed;
	left: 0;
	right: 0;
	top: var(--vv-top, 0px);
	z-index: 45;
	background: var(--bg);
}

/* ── Boot placeholder (index.html): the shell's outline until the first render replaces it. Each part has the
   size and place of the one that follows it — the rail on a desktop, the header and tab bar on touch chrome (the
   same media condition as below) — so the shell arrives without moving anything. ── */
.boot-shell {
	position: fixed;
	inset: 0;
	display: flex;
	background: var(--bg);
	background-image: var(--mud-palette-background-gradient);
}

.boot-rail {
	flex-shrink: 0;
	width: var(--rail-w);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-5) 0;
	box-sizing: border-box;
	background: var(--rail-bg);
	border-right: 1px solid var(--border-soft);
}

.boot-rail-logo,
.boot-rail-item {
	width: 44px;
	height: 44px;
	flex-shrink: 0;
	border-radius: 14px;
}

.boot-rail-sep {
	width: 28px;
	height: 1px;
	margin: 6px 0;
	background: color-mix(in srgb, var(--rail-ink) 45%, transparent);
}

.boot-col {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.boot-topbar,
.boot-bottomnav {
	display: none;
	flex-shrink: 0;
	background: var(--surface-3);
}

.boot-topbar {
	height: var(--topbar-h);
	align-items: center;
	padding: 0 var(--space-5);
	border-bottom: 1px solid var(--border-soft);
}

.boot-topbar-mark {
	width: 38px;
	height: 38px;
	border-radius: var(--radius);
}

.boot-bottomnav {
	height: calc(3.625rem + env(safe-area-inset-bottom));
	margin-top: auto;
	border-top: 1px solid var(--border-soft);
}

.boot-page {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	padding: var(--main-pad);
}

.boot-title {
	width: min(16rem, 60%);
	height: 30px;
	border-radius: 6px;
}

.boot-sub {
	width: min(28rem, 85%);
	height: 14px;
	margin-bottom: 14px;
	border-radius: 4px;
}

.boot-card {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	padding: var(--space-6);
	border-radius: var(--radius-card);
	background: var(--surface);
	border: 1px solid var(--border-soft);
}

.boot-line {
	height: 0.75rem;
	border-radius: 4px;
}

@media (max-width: 760px), (pointer: coarse) {
	.boot-rail { display: none; }
	.boot-topbar { display: flex; }
	.boot-bottomnav { display: block; }
}

/* A phone held sideways: the header is a rail down the left edge, as SHORT LANDSCAPE above makes it. */
@media (max-width: 760px) and (orientation: landscape) and (max-height: 32rem),
	(pointer: coarse) and (orientation: landscape) and (max-height: 32rem) {
	.boot-topbar {
		position: fixed;
		top: 0;
		bottom: 0;
		left: 0;
		width: calc(56px + env(safe-area-inset-left));
		height: auto;
		flex-direction: column;
		padding: max(var(--space-3), env(safe-area-inset-top)) 0 max(var(--space-3), env(safe-area-inset-bottom)) env(safe-area-inset-left);
		border-bottom: 0;
		border-right: 1px solid var(--border-soft);
		box-sizing: border-box;
	}
	.boot-col {
		box-sizing: border-box;
		padding-left: calc(var(--space-8) + env(safe-area-inset-left));
	}
}
