ui/leveleditorhud.razor.scss
// Shared theme: pulled in for the .square-btn letterbox buttons (and its palette vars). The
// editor panel chrome below keeps its own local palette.
@import "Theme.scss";

$bg: rgba( 18, 22, 28, 0.92 );
$panel: rgba( 30, 36, 46, 0.96 );
$line: rgba( 255, 255, 255, 0.12 );
$text: rgba( 235, 240, 245, 1 );
$muted: rgba( 170, 180, 190, 1 );
$accent: rgba( 90, 180, 235, 1 );
$on: rgba( 60, 120, 170, 1 );
$danger: rgba( 190, 70, 70, 1 );
$charmatch: rgba( 130, 205, 145, 1 );  // Character/search text naming a character (keep in sync with LevelBrowserOverlay)
$musicmatch: rgba( 130, 190, 220, 1 ); // Music text naming a real song (light blue, same L/C as $charmatch)

// Must be the component name: the panel's element name is its type, so a bare `root` selector never
// matched. That was invisible while absolute children anchored past the unstyled root to the screen,
// but engine 0ff19a62e3 anchors them to the parent — unmatched, this root is a zero-width box.
LevelEditorHud {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	font-family: "Roboto";
	font-size: 15px;
	color: $text;
}

.canvas {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	z-index: 0;
	pointer-events: all;
}

.shortcut-input {
	position: absolute;
	width: 0;
	height: 0;
	pointer-events: none;
}

// Floating width×height readout anchored over the obstacle in the level (left/top set inline as a
// screen %). Fixed width + centred text + negative margins so it sits centred just above the box top
// regardless of the label's text length. Never eats pointer input.
.dim-label {
	position: absolute;
	width: 120px;
	margin-left: -60px;   // centre horizontally on the anchor
	margin-top: -34px;    // lift above the box top edge
	pointer-events: none;
	justify-content: center;
	align-items: center;
	color: $text;
	font-size: 15px;
	font-weight: 700;
	text-align: center;
	white-space: nowrap;
	text-shadow: 0px 0px 4px rgba( 0, 0, 0, 0.9 );
}

.panel {
	position: absolute;
	top: 24px;
	z-index: 1;
	pointer-events: all;
	background-color: $panel;
	border: 1px solid $line;
	border-radius: 0;
	padding: 12px;
	flex-direction: column;
	gap: 6px;

	.section {
		width: 100%;
		flex-direction: column;
		gap: 6px;
		background-color: rgba( 255, 255, 255, 0.025 );
		padding: 2px;
	}

	// The daily-template controls live in their own visually distinct section (cool-tinted), so
	// they read as a separate tool from the load-id / import-JSON row above.
	.section.template-section {
		background-color: rgba( 90, 180, 235, 0.07 );
		padding: 4px 2px;
	}

	.heading {
		color: $accent;
		font-size: 11px;
		font-weight: 700;
		letter-spacing: 1px;
		margin-top: 6px;
		margin-bottom: 2px;
	}

	.hint {
		color: $muted;
		font-size: 12px;

		&.tutorial {
			font-size: 10px;
		}
	}

	.shortcut {
		margin-left: 4px;
		color: $muted;
		opacity: 0.35;
	}

	.error {
		color: $danger;
		font-size: 13px;
		white-space: pre-wrap;
	}

	// Publish success line (shares the error label's slot under the save row).
	.publish-status {
		color: rgba( 130, 190, 240, 1 );
		font-size: 10px;
		line-height: 12px;
		white-space: pre-wrap;
	}

	.row {
		flex-direction: row;
		gap: 6px;
		flex-wrap: wrap;
	}

	button {
		background-color: rgba( 255, 255, 255, 0.06 );
		border: 1px solid $line;
		border-radius: 0;
		padding: 4px 8px;
		color: $text;
		font-size: 14px;
		align-items: center;
		justify-content: center;

		&:hover {
			background-color: rgba( 255, 255, 255, 0.14 );
		}

		&.on {
			background-color: $on;
			border-color: $accent;
		}

		&.on:hover {
			background-color: rgba( 80, 150, 205, 1 );
		}

		&.on:active {
			background-color: rgba( 105, 180, 235, 1 );
		}

		&.disabled {
			pointer-events: none;
			opacity: 0.65;
		}

		// Inactive action buttons (blank level id/name or template name): drop the save/danger
		// tint so the greyed-out state reads at a glance.
		&.save.disabled,
		&.danger.disabled {
			background-color: rgba( 255, 255, 255, 0.05 );
			color: $muted;
		}

		&.danger { background-color: rgba( 150, 50, 50, 0.5 ); }
		&.danger:hover { background-color: $danger; }
		&.exit { background-color: rgba( 90, 70, 40, 0.5 ); }
		&.save { flex-grow: 1; background-color: rgba( 45, 125, 70, 0.55 ); }
		&.save:hover { background-color: rgba( 70, 170, 95, 0.95 ); }

		// Workshop publish (standalone save row): steam-y blue beside the green Save.
		&.publish { background-color: rgba( 45, 90, 140, 0.55 ); }
		&.publish:hover { background-color: rgba( 70, 130, 195, 0.95 ); }
		&.publish.disabled {
			background-color: rgba( 255, 255, 255, 0.05 );
			color: $muted;
		}
	}
}

.tools {
	left: 24px;
	width: 344px;

	.grid-reset {
		width: 28px;
		height: 28px;
		padding: 0;
		flex-shrink: 0;

		.micon {
			font-family: "Material Icons";
			font-size: 17px;
			color: $muted;
			pointer-events: none;
		}

		&:hover .micon {
			color: $text;
		}
	}

	.toolgrid {
		flex-direction: row;
		flex-wrap: wrap;
		gap: 6px;

		.tool {
			width: 148px;
			height: 40px;
			flex-direction: row;
			align-items: center;
			justify-content: flex-start;
			gap: 6px;
			padding: 4px 8px;
			font-size: 13px;

			.ticon {
				font-family: "Material Icons";
				font-size: 18px;
				color: $muted;
				flex-shrink: 0;
			}

			> span {
				white-space: nowrap;
			}

			&.on .ticon { color: $text; }
		}
	}

	.sel {
		color: $muted;
		font-size: 13px;
		margin-top: 4px;
	}
}

.inspector {
	right: 24px;
	bottom: 24px;
	width: 380px;
	padding: 2px 4px 2px 4px;
	overflow-y: scroll;
	overflow-x: hidden;

	> .section,
	> .top-save-row {
		flex-shrink: 0;
	}

	.field {
		flex-direction: row;
		align-items: center;
		gap: 8px;

		> label {
			width: 90px;
			color: $muted;
			font-size: 13px;
		}

		EditorTextEntry {
			flex-grow: 1;
			background-color: rgba( 0, 0, 0, 0.35 );
			border: 1px solid $line;
			border-radius: 0;
			padding: 4px 8px;
			color: $text;

			// Focus pop: the engine repaints a panel only when its style changes, so without a
			// :focus rule a clicked box shows no caret until the first keystroke.
			&:focus { border-color: $accent; }

			// Save-blocking id (interior spaces) reads red before the save button is even tried.
			&.invalid {
				color: rgba( 235, 110, 110, 1 );
				border-color: $danger;
			}

			// Character field: the text resolves to a character (exact id or display name) and is in effect.
			&.character-match { color: $charmatch; }

			// Music field: the typed name is a real song (light blue), or is a real song another mapped
			// level already uses (red — same accident the picker's music badge flags).
			&.music-match { color: $musicmatch; }
			&.music-dupe { color: rgba( 235, 110, 110, 1 ); }
		}

		// Dice button beside the Music field.
		.music-rand {
			width: 24px;
			height: 22px;
			padding: 0;
			flex-shrink: 0;
			align-items: center;
			justify-content: center;

			.micon {
				font-family: "Material Icons";
				font-size: 15px;
				color: $muted;
			}

			&:hover .micon { color: $text; }
		}

		&.wide {
			flex-direction: column;
			align-items: stretch;

			> label {
				width: auto;
			}
		}

		EditorTextEntry.multiline {
			min-height: 40px;
			white-space: pre-wrap;
		}

		&.pattern-rows {
			align-items: flex-start;

			EditorTextEntry.multiline {
				height: 32px;
				min-height: 32px;
			}
		}
	}

	.level-fields {
		flex-direction: row;
		align-items: center;
		gap: 8px;

		.field {
			flex-grow: 1;
			min-width: 0;
			gap: 4px;

			> label {
				width: auto;
				flex-shrink: 0;
			}

			EditorTextEntry {
				min-width: 0;
			}
		}

		.delete-level-actions {
			margin-left: auto;
			flex-direction: row;
			gap: 6px;
			flex-shrink: 0;
		}
	}

	.import-row {
		flex-direction: row;
		align-items: center;
		flex-wrap: nowrap;
		gap: 4px;

		button {
			padding: 4px;
			font-size: 11px;
			white-space: nowrap;
			flex-shrink: 0;
		}

		// The template row reuses the panel-wide green .save look, but stays row-sized (the
		// top-save-row variant grows to fill its bar).
		button.save {
			flex-grow: 0;
		}

		EditorTextEntry {
			min-width: 0;
			flex-grow: 1;
			background-color: rgba( 0, 0, 0, 0.35 );
			border: 1px solid $line;
			border-radius: 0;
			padding: 4px;
			color: $text;
			&:focus { border-color: $accent; }

			// Template name field: the typed name is already taken, so Save would overwrite that template.
			&.template-dupe { color: rgba( 235, 110, 110, 1 ); }
		}

		EditorTextEntry.multiline {
			height: 28px;
			min-height: 28px;
			max-height: 28px;
			overflow-y: scroll;
		}

		.copy-json {
			width: 28px;
			height: 28px;
			padding: 0;
			justify-content: center;

			.micon {
				font-family: "Material Icons";
				font-size: 16px;
			}
		}
		// Small numeric template-weight box; the name entry keeps the flexible width. No text-align
		// (it doesn't reach the TextEntry's inner label and squeezing the box clipped the value).
		EditorTextEntry.tweight {
			flex-grow: 0;
			width: 48px;
			min-width: 48px;
			padding: 4px 6px;
		}
	}

	.toggles .toggle {
		flex-grow: 1;
	}

	.refreshrow {
		flex-direction: row;
		justify-content: flex-end;

		.mini {
			width: 26px;
			height: 22px;
			padding: 0;
			flex-shrink: 0;

			.micon {
				font-family: "Material Icons";
				font-size: 16px;
				color: $muted;
			}

			&:hover .micon {
				color: $text;
			}
		}
	}

	.count {
		align-items: center;

		label {
			color: $muted;
		}

		.num {
			width: 40px;
			text-align: center;
			color: $text;
		}

		button {
			width: 34px;
		}
	}

	.environment-choice {
		flex-wrap: nowrap;

		> label:first-child {
			width: 76px;
			flex-shrink: 0;
		}

		.num {
			width: auto;
			min-width: 0;
			flex-grow: 1;
			white-space: nowrap;
			font-size: 12px;
		}
	}

	.environment-randomizers {
		justify-content: flex-end;

		button {
			width: 28px;
			height: 26px;
			padding: 0;
			flex-shrink: 0;
		}

		.micon {
			font-family: "Material Icons";
			font-size: 17px;
			color: $muted;
			pointer-events: none;
		}

		button:hover .micon {
			color: $text;
		}

		.alt-pattern-toggle {
			margin-right: auto;

			&.on {
				background-color: $on;
				border-color: $accent;
			}
		}
	}

	.alternate-choice {
		background-color: rgba( 235, 170, 60, 0.07 );
	}

	.custom-pattern-editor {
		flex-direction: column;
		gap: 4px;
		padding: 4px 0;

		.pattern-dimension .glabel {
			width: 96px;
			color: $muted;
		}
	}

	.chips {
		flex-direction: row;
		flex-wrap: wrap;
		//gap: 2px;

		.chip {
			padding: 4px 8px;
			font-size: 12px;
			background-color: rgba( 255, 255, 255, 0.08 );
		}

		// Block-type buttons drawn as the baked face art (sprites/ui/blocks/*, set inline). The art is
		// an opaque square, so hover/active feedback is a border tint rather than the background colour
		// (which the icon would cover). Border is present-but-transparent at rest so hovering never
		// shifts layout.
		// 60px so a gapless row of 6 spans the section's inner width exactly (found empirically:
		// the box arithmetic from the 380px panel suggested 61px, but the real rendered width is
		// 360px), keeping the grid flush against both edges instead of leaving the leftover as
		// fake right-side padding.
		.chip.bicon {
			width: 60px;
			height: 60px;
			padding: 0;
			border: 1px solid transparent;
			background-size: 100% 100%;
			background-repeat: no-repeat;
			image-rendering: pixelated;
			justify-content: flex-end;
			align-items: flex-start;

			// Corner ✕ on removable (pool) chips.
			.x {
				font-size: 12px;
				color: $text;
				margin: 2px 4px 0 0;
				text-shadow: 1px 1px 0px rgba( 0, 0, 0, 0.85 );
			}
		}

		.chip.bicon:hover {
			border-color: rgba( 255, 255, 255, 0.85 );
		}

		.chip.bicon:active {
			border-color: rgba( 110, 170, 225, 0.95 );
		}

		&.add .chip.add {
			background-color: rgba( 60, 90, 120, 0.4 );
		}

		&.add .chip.add:hover {
			background-color: rgba( 80, 130, 180, 0.75 );
		}

		&.add .chip.add:active {
			background-color: rgba( 110, 170, 225, 0.95 );
		}
	}

	.blocklist {
		flex-direction: column;
		gap: 4px;

		.blockrow {
			flex-direction: row;
			align-items: center;
			gap: 6px;
			background-color: rgba( 255, 255, 255, 0.05 );
			border: 1px solid $line;
			border-radius: 0;
			padding: 3px 4px;

			// Baked face-art stand-in for the old block-type text label (type name lives in the tooltip).
			.btype-icon {
				width: 34px;
				height: 34px;
				flex-shrink: 0;
				background-size: 100% 100%;
				background-repeat: no-repeat;
				image-rendering: pixelated;
			}

			.phase {
				flex-direction: row;
				align-items: center;
				gap: 2px;

				button {
					padding: 2px 6px;
					font-size: 13px;
				}

				.num {
					width: 30px;
					text-align: center;
					color: $muted;
					font-size: 13px;
					white-space: nowrap;
				}
			}

			.sides {
				flex-direction: row;
				gap: 2px;

				.sbtn {
					width: 22px;
					padding: 2px 0;
					font-size: 12px;
					color: $muted;
				}

				.sbtn.on {
					color: $text;
				}
			}

			.rm {
				margin-left: auto;
				padding: 2px 7px;
				color: $muted;
			}
		}
	}

	.code {
		background-color: rgba( 0, 0, 0, 0.5 );
		border: 1px solid $line;
		border-radius: 0;
		padding: 8px;
		font-family: "Consolas";
		font-size: 12px;
		color: rgba( 200, 230, 210, 1 );
		white-space: pre-wrap;
		max-height: 220px;
		overflow-y: scroll;
	}
}

// Grid controls (in the TOOLS panel). `.row.grid-row` (specificity ties `.panel .row` and comes
// later) so it can override the base row's `flex-wrap: wrap` — without nowrap the label + slider
// exceed the 240px panel and the slider wraps to a second line, leaving a tall vertical gap.
.row.grid-row {
	flex-direction: row;
	flex-wrap: nowrap;
	align-items: center;
	gap: 4px;

	.glabel {
		flex-shrink: 0;
		width: 96px;
		color: rgba( 255, 255, 255, 1 );
	}

	.slider-wrap {
		flex-grow: 1;
	}
}

.row.grid-row.ability-row {
	.glabel {
		width: 122px;
	}

	EditorTextEntry {
		min-width: 0;
		flex-grow: 1;
		background-color: rgba( 0, 0, 0, 0.35 );
		border: 1px solid $line;
		border-radius: 0;
		padding: 4px 8px;
		color: $text;
		&:focus { border-color: $accent; }
	}
}

// Square home button in the bottom-left letterbox: returns to the main menu. All the square
// buttons here get their look from the shared .square-btn (Theme.scss); these rules add ONLY
// position and the per-button accent colours.
.square-btn.home-button {
	position: absolute;
	z-index: 1;
	bottom: 40px;
	left: 40px;
	border-color: #ff7b7b;

	.img { background-tint: #ff9b9b; }
}

// Square back button, stacked directly ABOVE the home button (bottom row is at 40px, 72px tall +
// 12px gap): returns to the workshop screen.
.square-btn.back-button {
	position: absolute;
	z-index: 1;
	bottom: 124px;
	left: 40px;
	border-color: #b79bff;

	.img { background-tint: #cbb8ff; }
}

// Square clear button, immediately right of the home button (home is left:40px, 72px wide + 12px gap):
// resets the editor to a fresh default level.
.square-btn.clear-button {
	position: absolute;
	z-index: 1;
	bottom: 40px;
	left: 124px;
	border-color: #e0b050;

	.img { background-tint: #f0cf80; }
}

// Square level-browser button immediately left of Test Play. It shares the left-letterbox anchor: its
// right edge sits one button width plus a 12px gap further left than Test Play's 560px offset.
.square-btn.browse-button {
	position: absolute;
	z-index: 1;
	bottom: 40px;
	right: 50%;
	// One button-width (72) + 12px gap left of Test Play's 580px inset.
	margin-right: 664px;
	border-color: #7bb8ff;

	.img { background-tint: #9bcbff; }
}

// Character picker in the LEFT letterbox, sitting above the test-play button. Anchored to screen centre
// so it hugs the square edge on any aspect (same trick as the test-play button): right:50% puts its
// right edge at centre, then margin-right pushes it left past the square half-width (540) + a gap.
.character-slot {
	position: absolute;
	z-index: 1000;
	right: 50%;
	margin-right: 580px;
	bottom: 130px;
	align-items: flex-end;
}

// Square reload-characters button on the right side of the LEFT letterbox, directly above the character
// picker. Shares the picker's screen-centre anchor (right:50% + margin-right:560px) so its right edge
// lines up with the picker; sits above the 100px picker trigger (bottom:130px) plus a gap.
.square-btn.reload-characters-button {
	position: absolute;
	z-index: 1;
	bottom: 242px;
	right: 50%;
	margin-right: 580px;
	border-color: #c79bff;

	.img { background-tint: #d9bcff; }
}

// Square test-play button at the bottom-right of the LEFT letterbox (just left of the centred square).
.square-btn.test-play-button {
	position: absolute;
	z-index: 1;
	bottom: 40px;
	right: 50%;
	// 580 = square half-width (540) + 40, so the gap to the square edge matches the 40px bottom inset.
	margin-right: 580px;
	border-color: #7bff8f;

	.img { background-tint: #9bff9b; }
}