🔍 s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (118 total matches found)
dogecoin.ssoundboard / ui/controls/color/colorhuecontrol.cs.scss
Game game
ColorHueControl
{
	gap: 0.5rem;
	flex-grow: 1;
	pointer-events: all;
	background: linear-gradient( to right, red, yellow, lime, cyan, blue, magenta, red );
	border-radius: 4px;
	padding: 2px;
	height: 12px;
	position: relative;
	cursor: pointer;
	margin: 10px 0;
	border: 1px solid #333;

	&:hover
	{
		border: 1px solid #08f;
	}

	&:active
	{
		border: 1px solid #fff;
	}

	.handle
	{
		top: -5px;
		bottom: -5px;
		aspect-ratio: 1;
		border-radius: 100px;
		border: 2px solid #444;
		position: absolute;
		background-color: white;
		box-shadow: 2px 2px 16px #000a;
		transform: translateX( -50% );
		pointer-events: none;
	}
}
dogecoin.ssoundboard / styles/rootpanel.scss
Game game

RootPanel
{
	font-family: Roboto;
	color: white;
}

IconPanel, i
{
	font-family: Material Icons;
	background-position: center;
	background-size: contain;
}

.tooltip
{
	background-color: rgba( black, 0.8 );
	border: 1px solid rgba( #555, 0.1 );
	padding: 10px;
	border-radius: 4px;
	color: white;
	font-family: Inter;
	font-weight: bold;
	transition: opacity 0.09s ease;
	opacity: 1;
	margin: 0px;

	&:intro, &:outro
	{
		opacity: 0;
	}
}

.textentry
{
	cursor: text;
	align-items: center;
	justify-content: center;
	white-space: nowrap;
	overflow: hidden;
	flex-direction: row;
	position: relative;
	padding: 10px;

	&.disabled
	{
		cursor: default;
	}

	&.is-multiline
	{
		align-items: flex-start;
		white-space: normal;
	}

	.content-label,
	.placeholder
	{
		flex-grow: 1;
	}

	.placeholder
	{
		opacity: 0.2;
		pointer-events: none;
	}

	.iconpanel
	{
		position: absolute;
		right: 0;
		padding-right: 10px;
		pointer-events: none;

		&.clearbutton
		{
			color: #fff;
			pointer-events: all;
			cursor: pointer;
			opacity: 0.1;

			&:hover
			{
				opacity: 0.5;
			}

			&:active
			{
				opacity: 1;
			}
		}
	}

	&:empty
	{
		.clearbutton
		{
			display: none;
		}
	}

	&.has-icon
	{
		padding-right: 30px;
	}

	.content-label,
	.prefix-label,
	.suffix-label,
	.placeholder
	{
		overflow: hidden;
	}

	.content-label
	{
		order: 1;
	}

	.suffix-label
	{
		order: 2;
	}

	.prefix-label,
	.suffix-label
	{
		flex-shrink: 0;
		opacity: 0.5;
	}

	&.invalid
	{
		box-shadow: 0px 0px 1px 2px #f8356b;
	}
}

.popup-panel
{
	background-color: #1b202c;
	flex-direction: column;
	color: #8e9ec2;
	transition: all 0.1s ease-out;
	position: absolute;
	z-index: 2000;
	min-height: 20px;
	min-width: 10px;
	overflow: scroll;
	pointer-events: all;
	box-shadow: 3px 3px 64px #000;
	border-radius: 4px;

	&.medium
	{
		max-width: 300px;
	}

	&.center-horizontal
	{
		transform-origin: top center;
	}

	&.below-center
	{
		transform: translateX( -50% );
		transform-origin: 0% 0%;
	}

	&.above-left
	{
		transform-origin: 0% 100%;
	}

	&.above-center
	{
		transform: translateX( -50% );
		transform-origin: 0% 100%;
	}

	&.left
	{
		transform-origin: 100% 50%;
		transform: translate( 00% -50% );
	}

	&.right
	{
		transform-origin: 0 50%;
		transform: translate( 0% -50% );
	}

	&:intro
	{
		transform: scale( 0 );
		pointer-events: none;
		opacity: 0;
	}

	&:outro
	{
		opacity: 0;
		transition: all 0.1s ease-in;
		margin-top: 50px;
		pointer-events: none;
	}

	&.below-stretch
	{
		transform-origin: 0% 0%;

		&:intro
		{
			transform: scaleY( 0.1 );
		}
	}

	> .canvas
	{
		flex-direction: column;
	}

	.information
	{
		padding: 16px;
		font-size: 14px;
		opacity: 0.5;
	}

	button
	{
		padding: 2px 8px;
		cursor: pointer;
		font-family: Poppins;
		font-weight: 600;
		flex-shrink: 0;

		&:hover
		{
			background-color: #08f;
			color: #fff;
		}
	}
}
dogecoin.ssoundboard / ui/components/anchorpad/anchorpad.razor.scss
Game game
@import "/ui/styles/_theme.scss";

.anchor-pad {
	width: 130px;
	height: 90px;
	background-color: $bg-2;
	border: 1px solid $line-strong;
	padding: 6px;
	position: relative;

	.anchor-cell {
		pointer-events: all;
		background-color: rgba(255,255,255,0.04);
		border: 1px solid $line-strong;
		cursor: pointer;
		position: absolute;
		width: 34px;
		height: 22px;
		transition: background-color 0.1s ease, border-color 0.1s ease;

		&:hover { background-color: rgba(255,255,255,0.1); border-color: $accent; }

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

		&.anchor-topleft     { left: 6px;  top: 6px; }
		&.anchor-topright    { right: 6px; top: 6px; }
		&.anchor-bottomleft  { left: 6px;  bottom: 6px; }
		&.anchor-bottomright { right: 6px; bottom: 6px; }
		&.anchor-center      { left: 50%; top: 50%; transform: translate(-50%, -50%); }
	}
}
dogecoin.ssoundboard / ui/components/hintbox/hintbox.scss
Game game
@import "/ui/styles/_theme.scss";

.hint-box {
	padding: 14px 16px;
	background-color: rgba(98, 200, 255, 0.07);
	border-left: 3px solid $info;
	color: $text-dim;
	font-size: $fs-3;
	font-weight: 500;
	letter-spacing: 0.2px;
	code { color: $info; font-family: $font-mono; font-weight: 700; }
}
dogecoin.ssoundboard / ui/components/tile/tile.scss
Game game
@import "/ui/styles/_theme.scss";

.tile {
	flex-direction: column;
	width: 140px;
	padding: 0;
	background-color: $bg-2;
	border: 1px solid $line-strong;
	overflow: hidden;
	cursor: pointer;
	transition: border-color 0.1s ease;

	.tile-swatch {
		width: 100%;
		height: 80px;
		position: relative;
		overflow: hidden;
		background-color: #1a1f2e;
		background-size: cover;
		background-position: center;
	}

	.tile-thumb {
		width: 100%;
		height: 100%;
		object-fit: contain;
	}

	.tile-placeholder {
		width: 100%;
		height: 100%;
	}

	.tile-vignette {
		position: absolute;
		left: 0; top: 0; right: 0; bottom: 0;
		background-color: rgba(0,0,0,0.25);
		pointer-events: none;
	}

	.tile-label {
		height: 42px;
		padding: 8px 10px;
		flex-shrink: 0;
		align-items: center;
		justify-content: center;
		font-size: $fs-1;
		font-weight: 700;
		color: $text-dim;
		text-align: center;
		font-family: $font-mono;
		letter-spacing: 1px;
		text-transform: uppercase;
		border-top: 1px solid $line;
	}

	&:hover {
		border-color: $accent;
		.tile-label { color: $text; }
	}
	&.active {
		border-color: $accent;
		.tile-label { color: $text; background-color: $accent-soft; font-weight: 800; }
	}
}
dogecoin.ssoundboard / ui/components/vector3field/vector3field.razor.scss
Game game
@import "/ui/styles/_theme.scss";
@import "/ui/components/field/field.scss";

.v3-field {
	.v3-row {
		flex-direction: row;
		gap: 6px;
		align-items: stretch;
		width: 100%;
	}
}
dogecoin.ssoundboard / ui/components/packageflairbar.razor.scss
Game game
PackageFlairBar
{
	position: absolute;
	top: 6px;
	left: 6px;
	z-index: 2;
	flex-direction: row;
	gap: 5px;
	pointer-events: none;

	.flair
	{
		position: relative;
		align-items: center;
		justify-content: center;
		width: 22px;
		height: 22px;
		pointer-events: all;
		border-radius: 4px;
		color: white;
		border: 1px solid rgba( white, 0.1 );
		outline: 1px solid #0006;
		opacity: 0.7;

		&:hover
		{
			opacity: 1;
		}

		icon
		{
			font-size: 15px;
		}
	}
}
dogecoin.ssoundboard / soundboardpanel.razor.scss
Game game
@import "/UI/Styles/_Theme.scss";
@import "/UI/Styles/_Fonts.scss";
@import "/UI/Components/Tile/Tile.scss";
@import "/UI/Components/Button/Button.scss";

SoundBoardPanel
{
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	pointer-events: all;
	overflow: hidden;

	.sb
	{
		width: 100%;
		height: 100%;
		flex-direction: column;
		background-color: $bg-0;
		overflow: hidden;
	}

	.sb-shell
	{
		width: 100%;
		height: 100%;
		flex-direction: column;
		flex-grow: 1;
		flex-shrink: 1;
		min-height: 0;
		padding: 20px 24px 18px;
		overflow: hidden;
	}

	.sb-topbar
	{
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		height: 64px;
		min-height: 64px;
		max-height: 64px;
		margin-bottom: 14px;
		padding-bottom: 14px;
		border-bottom: 1px solid $line;
		flex-shrink: 0;
		flex-wrap: nowrap;
		overflow: hidden;
	}

	.sb-brand-block
	{
		display: flex;
		align-items: center;
		gap: 12px;
		min-width: 0;
		flex-grow: 1;
	}

	.sb-status-led
	{
		width: 10px;
		height: 10px;
		border-radius: 50%;
		background-color: $bg-2;
		border: 1px solid $line;
		flex-shrink: 0;

		&.is-live
		{
			background-color: $success;
			border-color: $success-line;
		}
	}

	.sb-brand-copy
	{
		display: flex;
		flex-direction: column;
		gap: 2px;
		min-width: 0;
	}

	.sb-eyebrow
	{
		font-size: $fs-1;
		font-weight: 700;
		letter-spacing: $ls-xwide;
		color: $text-faint;
		font-family: $font-mono;
	}

	.sb-title
	{
		font-size: $fs-5;
		font-weight: 800;
		color: $text;
		font-family: $font-primary;
		white-space: nowrap;
		overflow: hidden;
	}

	.sb-voice-chip
	{
		display: flex;
		flex-direction: column;
		justify-content: center;
		gap: 6px;
		width: 128px;
		height: 52px;
		min-width: 128px;
		max-width: 128px;
		padding: 8px 12px;
		border-radius: 4px;
		background-color: $bg-2;
		border: 1px solid $line-strong;
		flex-shrink: 0;
		overflow: hidden;
	}

	.sb-voice-chip-head
	{
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		gap: 8px;
	}

	.sb-voice-chip-label
	{
		font-size: $fs-1;
		font-weight: 700;
		letter-spacing: $ls-wide;
		color: $text-faint;
		font-family: $font-mono;
	}

	.sb-voice-chip-value
	{
		font-size: $fs-4;
		font-weight: 800;
		line-height: 1;
		color: $accent;
		font-family: $font-mono;
	}

	.sb-voice-meter-stack
	{
		width: 100%;
		height: 18px;
		min-height: 18px;
		max-height: 18px;
		flex-direction: column;
		justify-content: flex-end;
		gap: 2px;
		overflow: hidden;
	}

	.sb-voice-meter-tier
	{
		width: 100%;
		height: 3px;
		min-height: 3px;
		max-height: 3px;
		border-radius: 999px;
		background-color: $bg-inset;
		overflow: hidden;
	}

	.sb-voice-meter-fill
	{
		height: 100%;
		border-radius: 999px;
		transition: width 0.16s ease;
	}

	.sb-tabs-bar
	{
		display: flex;
		flex-direction: row;
		flex-shrink: 0;
		align-items: center;
		gap: 8px;
		width: 100%;
		height: 48px;
		margin-bottom: 10px;
		pointer-events: all;
	}

	.sb-search-bar
	{
		display: flex;
		flex-direction: row;
		flex-shrink: 0;
		align-items: center;
		gap: 10px;
		width: 100%;
		height: 40px;
		margin-bottom: 12px;
		padding: 0 12px;
		border-radius: 8px;
		background-color: $bg-2;
		border: 1px solid $line-strong;
		pointer-events: all;
	}

	.sb-search-icon
	{
		flex-shrink: 0;
		font-size: 16px;
		font-weight: 700;
		color: $text-faint;
		line-height: 1;
	}

	.sb-search-input
	{
		flex-grow: 1;
		flex-shrink: 1;
		min-width: 0;
		height: 36px;
		min-height: 36px;
		padding: 0 4px;
		background-color: transparent;
		border: 0;
		color: $text;
		font-size: $fs-2;
		font-weight: 600;
		font-family: $font-mono;
		letter-spacing: 0.04em;
	}

	.sb-search-clear
	{
		display: flex;
		flex-shrink: 0;
		align-items: center;
		justify-content: center;
		width: 28px;
		height: 28px;
		border-radius: 6px;
		background-color: $bg-inset;
		border: 1px solid $line;
		color: $text-dim;
		font-size: 12px;
		font-weight: 800;
		cursor: pointer;
		pointer-events: all;

		&:hover
		{
			border-color: $accent;
			color: $accent;
		}

		&:active
		{
			sound-in: "ui.button.press";
			transform: scale(0.96);
		}
	}

	.sb-search-count
	{
		flex-shrink: 0;
		min-width: 36px;
		text-align: right;
		font-size: $fs-1;
		font-weight: 700;
		letter-spacing: $ls-wide;
		color: $text-faint;
		font-family: $font-mono;
		white-space: nowrap;
	}

	.sb-empty
	{
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: 8px;
		width: 100%;
		min-height: 180px;
		padding: 32px 16px;
		pointer-events: none;
	}

	.sb-empty-title
	{
		font-size: $fs-4;
		font-weight: 800;
		color: $text-dim;
		font-family: $font-mono;
		letter-spacing: 0.08em;
	}

	.sb-empty-body
	{
		font-size: $fs-2;
		font-weight: 500;
		color: $text-faint;
		font-family: $font-mono;
		text-align: center;
	}

	.sb-tabs-arrow
	{
		display: flex;
		flex-shrink: 0;
		align-items: center;
		justify-content: center;
		width: 36px;
		height: 40px;
		border-radius: 8px;
		background-color: $bg-2;
		border: 1px solid $line-strong;
		color: $text;
		font-size: 22px;
		font-weight: 800;
		cursor: pointer;
		pointer-events: all;
		z-index: 2;

		&:hover
		{
			border-color: $accent;
			color: $accent;
		}

		&:active
		{
			sound-in: "ui.button.press";
			transform: scale(0.96);
		}

		&.is-disabled
		{
			opacity: 0.35;
			cursor: default;
			pointer-events: none;
		}
	}

	.sb-tabs-scroll
	{
		flex-grow: 1;
		flex-shrink: 1;
		min-width: 0;
		height: 100%;
		// Clip the track; scrolling is done via margin-left on .sb-tabs-track
		// because Panel.ScrollSize often stays 0 for horizontal flex content.
		overflow: hidden;
		pointer-events: all;
	}

	.sb-tabs-track
	{
		display: flex;
		flex-direction: row;
		flex-wrap: nowrap;
		align-items: center;
		gap: 6px;
		// Must grow with chips so content width > viewport (enables overflow).
		width: auto;
		flex-shrink: 0;
		height: 100%;
		padding: 2px 2px 4px;
		// margin-left is driven from code for arrow / wheel scrolling.
	}

	.sb-tab-chip
	{
		display: flex;
		flex-direction: row;
		flex-shrink: 0;
		align-items: center;
		justify-content: center;
		gap: 6px;
		height: 36px;
		padding: 0 14px;
		border-radius: 6px;
		background-color: $bg-2;
		border: 1px solid $line-strong;
		cursor: pointer;
		pointer-events: all;
		white-space: nowrap;

		&:hover
		{
			border-color: $accent;
		}

		&.is-active
		{
			background-color: $accent;
			border-color: $accent;
			color: $accent-ink;

			.sb-tab-chip-label
			{
				color: $accent-ink;
			}
		}

		&.is-search
		{
			background-color: #2a6df4;
			border-color: #5b8dff;
			color: #fff;

			.sb-tab-chip-label
			{
				color: #fff;
			}
		}

		&:active
		{
			sound-in: "ui.button.press";
			transform: scale(0.97);
		}
	}

	.sb-tab-chip-icon
	{
		font-size: 13px;
		flex-shrink: 0;
	}

	.sb-tab-chip-label
	{
		font-size: $fs-2;
		font-weight: 700;
		font-family: $font-mono;
		letter-spacing: 1.2px;
		color: $text-dim;
		white-space: nowrap;
		flex-shrink: 0;
	}

	.sb-board
	{
		flex-grow: 1;
		flex-shrink: 1;
		min-height: 0;
		margin-bottom: 14px;
		border: 1px solid $line;
		background-color: $bg-1;
		flex-direction: column;
		overflow: hidden;
		pointer-events: all;
	}

	.sb-board-scroll
	{
		flex-grow: 1;
		flex-shrink: 1;
		min-height: 0;
		flex-direction: column;
		overflow-y: scroll;
		overflow-x: hidden;
		pointer-events: all;
	}

	.sb-pad-content
	{
		flex-direction: column;
		flex-shrink: 0;
		width: 100%;
		pointer-events: all;
	}

	.tile-grid
	{
		flex-direction: row;
		flex-wrap: wrap;
		align-content: flex-start;
		width: 100%;
		flex-shrink: 0;
		gap: 12px;
		padding: 8px;
		pointer-events: all;

		.tile
		{
			pointer-events: all;
			flex-shrink: 0;
		}
	}

	.sb-pad-grid
	{
		padding: 4px 4px 24px;
	}

	.tile
	{
		width: 112px;

		&.is-loading
		{
			border-color: $line-strong;

			.tile-swatch
			{
				opacity: 0.55;
			}

			.tile-label
			{
				color: $text-dim;
			}
		}

		// Lobby co-op: pad lit by someone in the lobby (avatar + username).
		&.is-remote
		{
			border-color: #5b8dff;

			.tile-label
			{
				color: #e8f0ff;
				background-color: rgba(42, 109, 244, 0.45);
				font-weight: 800;
			}
		}
	}

	.tile-swatch
	{
		height: 64px;
		position: relative;
	}

	.tile-remote-avatar
	{
		position: absolute;
		top: 4px;
		right: 4px;
		width: 28px;
		height: 28px;
		border-radius: 50%;
		background-size: cover;
		background-position: center;
		background-color: #0b1220;
		border: 2px solid #ffffff;
		z-index: 3;
		pointer-events: none;
	}

	.tile-remote-name
	{
		position: absolute;
		left: 3px;
		right: 3px;
		bottom: 3px;
		padding: 2px 4px;
		border-radius: 4px;
		background-color: rgba(0, 0, 0, 0.72);
		color: #ffffff;
		font-size: 9px;
		font-weight: 800;
		font-family: $font-mono;
		letter-spacing: 0.04em;
		text-align: center;
		text-transform: none;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
		z-index: 3;
		pointer-events: none;
	}

	.sb-controls
	{
		display: flex;
		flex-direction: row;
		align-items: center;
		gap: 12px;
		flex-shrink: 0;
		min-height: 56px;
	}

	// LOOP + CO-OP share one baseline.
	.sb-toggle-row
	{
		display: flex;
		flex-direction: row;
		align-items: center;
		flex-shrink: 0;
		gap: 10px;
	}

	.sb-toggle
	{
		display: flex;
		align-items: center;
		justify-content: center;
		flex-shrink: 0;
		width: 100px;
		height: 44px;
		min-width: 100px;
		min-height: 44px;
		max-height: 44px;
		padding: 0 12px;
		border-radius: 10px;
		font-size: 13px;
		font-weight: 900;
		letter-spacing: 0.16em;
		color: #ffffff;
		cursor: pointer;
		pointer-events: all;
		// s&box UI: use border shorthand only (border-style is invalid).
		border: 3px solid #2a0000;
		background-color: #c10f0f;
		box-shadow: 0 3px 0 #3a0000, inset 0 1px 4px rgba(255, 255, 255, 0.2);
		text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);

		&.is-on
		{
			border: 3px solid #0a3d14;
			background-color: #1fbf3a;
			box-shadow: 0 3px 0 #0f5a1c, 0 0 14px rgba(57, 255, 80, 0.35), inset 0 1px 4px rgba(255, 255, 255, 0.25);
		}

		&.is-off
		{
			border: 3px solid #2a0000;
			background-color: #c10f0f;
			box-shadow: 0 3px 0 #3a0000, inset 0 1px 4px rgba(255, 255, 255, 0.2);
		}

		// CO-OP on uses same green as LOOP (is-on); is-coop is optional marker.
		&.is-on.is-coop
		{
			border: 3px solid #0a3d14;
			background-color: #1fbf3a;
			box-shadow: 0 3px 0 #0f5a1c, 0 0 14px rgba(57, 255, 80, 0.35), inset 0 1px 4px rgba(255, 255, 255, 0.25);
		}

		&:active
		{
			sound-in: "ui.button.press";
			transform: translateY(2px);
		}
	}

	.sb-now-playing
	{
		flex-grow: 1;
		display: flex;
		align-items: center;
		gap: 10px;
		min-width: 0;
		padding: 10px 14px;
		background-color: $bg-2;
		border: 1px solid $line;
	}

	.sb-now-playing-dot
	{
		width: 8px;
		height: 8px;
		border-radius: 50%;
		background-color: $line-strong;
		flex-shrink: 0;

		&.is-live
		{
			background-color: $accent;
		}
	}

	.sb-now-playing-text
	{
		font-size: $fs-2;
		font-weight: 600;
		color: $text-dim;
		font-family: $font-mono;
		white-space: nowrap;
		overflow: hidden;
	}

	.sb-kill-btn
	{
		min-width: 148px;
		flex-shrink: 0;
	}

	&.is-wide
	{
		.tile
		{
			width: 120px;
		}
	}

	&.is-medium
	{
		.sb-shell
		{
			padding: 18px 20px 16px;
		}

		.sb-title
		{
			font-size: $fs-4;
		}

		.tile
		{
			width: 104px;
		}
	}

	&.is-compact
	{
		.sb-shell
		{
			padding: 14px 14px 12px;
		}

		.sb-voice-chip
		{
			width: 116px;
			min-width: 116px;
			max-width: 116px;
		}

		.tile
		{
			width: 96px;
		}

		.tile-swatch
		{
			height: 56px;
		}
	}

	&.is-tight
	{
		.sb-shell
		{
			padding: 12px 10px 10px;
		}

		.sb-search-bar
		{
			height: 36px;
			padding: 0 10px;
		}

		.sb-search-count
		{
			min-width: 28px;
			font-size: 10px;
		}

		.sb-topbar
		{
			height: 58px;
			min-height: 58px;
			max-height: 58px;
		}

		.sb-voice-chip
		{
			width: 108px;
			min-width: 108px;
			max-width: 108px;
			height: 48px;
			min-height: 48px;
			max-height: 48px;
		}

		.sb-voice-chip-value
		{
			font-size: $fs-3;
		}

		.sb-title
		{
			font-size: $fs-3;
		}

		.sb-tabs-bar
		{
			height: 44px;
		}

		.sb-tab-chip
		{
			height: 32px;
			padding: 0 10px;
		}

		.sb-tab-chip-label
		{
			font-size: 11px;
		}

		.tile
		{
			width: 88px;
		}

		.tile-swatch
		{
			height: 50px;
		}

		.sb-controls
		{
			flex-wrap: wrap;
		}

		.sb-now-playing
		{
			width: 100%;
			order: 3;
		}

		.sb-kill-btn
		{
			width: 100%;
			order: 2;
		}
	}

	&.has-voices
	{
		.sb-topbar
		{
			border-bottom-color: $accent-line;
		}
	}

	.sb-ach
	{
		position: absolute;
		left: 24px;
		bottom: 28px;
		display: flex;
		flex-direction: row;
		align-items: center;
		gap: 14px;
		max-width: 420px;
		padding: 14px 18px;
		border-radius: 12px;
		background-color: rgba(12, 8, 20, 0.94);
		border: 2px solid rgba(255, 210, 80, 0.75);
		box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55), 0 0 24px rgba(255, 180, 40, 0.22);
		pointer-events: none;
		z-index: 50;
	}

	.sb-ach-icon
	{
		font-size: 28px;
		flex-shrink: 0;
	}

	.sb-ach-copy
	{
		display: flex;
		flex-direction: column;
		gap: 3px;
		min-width: 0;
	}

	.sb-ach-kicker
	{
		font-size: 9px;
		font-weight: 800;
		letter-spacing: 0.2em;
		color: #ffd24a;
	}

	.sb-ach-title
	{
		font-size: 15px;
		font-weight: 900;
		letter-spacing: 0.04em;
		color: #fff;
	}

	.sb-ach-body
	{
		font-size: 11px;
		font-weight: 500;
		letter-spacing: 0.02em;
		color: rgba(255, 255, 255, 0.72);
		white-space: normal;
	}
}
dogecoin.ssoundboard / UI/Components/Modal/ModalPanel.razor
Game game
@namespace Sunless.Libraries.UI
@using Sandbox
@using Sandbox.UI
@attribute [StyleSheet]
@inherits Panel

<root class="modal-overlay @(IsOpen ? "is-open" : "") @ExtraClass">
	@if ( IsOpen )
	{
		<div class="modal-scrim" @onclick=@HandleScrimClick></div>
		<div class="modal-dialog" style=@DialogStyle>
			@if ( Header is not null || !string.IsNullOrEmpty( Title ) || ShowClose )
			{
				<div class="modal-header">
					@if ( Header is not null )
					{
						@Header
					}
					else
					{
						<div class="modal-title">@Title</div>
					}
					@if ( ShowClose )
					{
						<div class="modal-close" @onclick=@HandleClose>X</div>
					}
				</div>
			}

			@if ( Body is not null )
			{
				<div class="modal-body">@Body</div>
			}

			@if ( Footer is not null )
			{
				<div class="modal-footer">@Footer</div>
			}
		</div>
	}
</root>

@code
{
	public bool IsOpen { get; set; }
	public string Title { get; set; }
	public bool ShowClose { get; set; } = true;
	public bool DismissOnScrim { get; set; } = true;
	public string ExtraClass { get; set; }
	public string DialogStyle { get; set; }
	public Action OnClose { get; set; }

	public RenderFragment Header { get; set; }
	public RenderFragment Body { get; set; }
	public RenderFragment Footer { get; set; }

	void HandleClose()
	{
		try { OnClose?.Invoke(); } catch ( Exception e ) { Log.Warning( $"[Modal] OnClose threw: {e.Message}" ); }
	}

	void HandleScrimClick()
	{
		if ( !DismissOnScrim ) return;
		HandleClose();
	}

	protected override int BuildHash() => HashCode.Combine( IsOpen, Title, ShowClose, ExtraClass, DialogStyle );
}
dogecoin.ssoundboard / UI/Components/Toast/Toast.cs
Game game
namespace Sunless.Libraries.UI;

using System;
using Sandbox;

// Singleton toast state. Host <ToastHost /> once. Call:
//
//   Toaster.Show( "#toast.saved" );
//   Toaster.New().WithIcon( "save" ).WithCopy( path ).Show( "#toast.saved", path );
//
public static class Toaster
{
	public static string Title { get; private set; }
	public static string Message { get; private set; }
	public static string Icon { get; private set; }
	public static bool ShowCopy { get; private set; }
	public static string CopyValue { get; private set; }
	public static ToastTone Tone { get; private set; }
	public static bool IsOpen => !string.IsNullOrEmpty( Message );
	public static int Version { get; private set; }

	public static Action OnShowSound { get; set; }
	public static Action OnCopySound { get; set; }

	public static ToastBuilder New() => new();

	public static void Show( string message, string title = null )
		=> New().Show( message, title );

	public static void Dismiss()
	{
		if ( !IsOpen ) return;
		Message = null;
		Title = null;
		Icon = null;
		CopyValue = null;
		ShowCopy = false;
		Tone = ToastTone.Default;
		Version++;
	}

	internal static void RaiseCopySound()
	{
		try { OnCopySound?.Invoke(); } catch ( Exception e ) { Log.Warning( $"[Toaster] copy sound threw: {e.Message}" ); }
	}

	internal static void Commit( ToastBuilder b )
	{
		Message = b._message;
		Title = b._title;
		Icon = b._icon;
		ShowCopy = b._showCopy;
		CopyValue = b._copyValue;
		Tone = b._tone;
		Version++;
		try { (b._onShowSound ?? OnShowSound)?.Invoke(); } catch ( Exception e ) { Log.Warning( $"[Toaster] show sound threw: {e.Message}" ); }
	}
}

public enum ToastTone { Default, Ok, Warn, Danger, Info }

public sealed class ToastBuilder
{
	internal string _title;
	internal string _message;
	internal string _icon;
	internal bool _showCopy;
	internal string _copyValue;
	internal ToastTone _tone = ToastTone.Default;
	internal Action _onShowSound;

	public ToastBuilder WithTitle( string title ) { _title = title; return this; }
	public ToastBuilder WithIcon( string ligature ) { _icon = ligature; return this; }
	public ToastBuilder WithCopy( string value = null ) { _showCopy = true; _copyValue = value; return this; }
	public ToastBuilder WithTone( ToastTone tone ) { _tone = tone; return this; }
	public ToastBuilder WithSound( Action sound ) { _onShowSound = sound; return this; }

	public void Show( string message, string title = null )
	{
		_message = message;
		if ( title is not null ) _title = title;
		Toaster.Commit( this );
	}
}
dogecoin.ssoundboard / styles/base.scss
Game game
@import "base/_splitcontainer.scss";
@import "base/_navigator.scss";

button
{
	cursor: pointer;
}

IconPanel
{
	font-family: Material Icons;
}

.is-half
{
	width: 50%;
}

.is-third
{
	width: 33%;
}

.is-quarter
{
	width: 25%;
}

button.has-subtitle
{
	position: relative;
	flex-direction: column;
	justify-content: flex-start;
	align-items: flex-start;
	padding-left: 40px; // icon space

	.iconpanel
	{
		position: absolute;
		left: 5px;
		top: 0;
		bottom: 0;
		align-items: center;
	}

	.button-label
	{
		font-weight: bold;
	}

	.button-subtitle
	{
		font-size: 12px;
		opacity: 0.5;
		mix-blend-mode: lighten;
	}
}
dogecoin.ssoundboard / ui/controlsheet/controlsheetrow.cs.scss
Game game
ControlSheetRow
{
	flex-direction: row;
	flex-shrink: 0;
	max-height: 32px;
	border-radius: 4px;

	&.hidden
	{
		display: none;
	}
}

ControlSheetRow > .left
{
	width: 150px;
	flex-shrink: 0;
	flex-grow: 0;
	padding: 6px;
}

ControlSheetRow > .right
{
	flex-grow: 1;
}

ControlSheetRow > .left > .title
{
	width: 150px;
	flex-shrink: 0;
	flex-grow: 0;
	font-size: 13px;
	white-space: nowrap;
	overflow: hidden;
}

ControlSheetRow > .right > textentry,
ControlSheetRow > .right numberentry
{
	background-color: #000a;
	flex-grow: 1;
	padding: 4px 8px;
	border-radius: 2px;
}

dogecoin.ssoundboard / ui/components/floatdock/floatdock.scss
Game game
@import "/ui/styles/_theme.scss";

.float-dock {
	pointer-events: all;
	background-color: $bg-1;
	border: 1px solid $line-strong;
	border-left: 2px solid @import "/ui/styles/_theme.scss";

.float-dock {
	pointer-events: all;
	background-color: $bg-1;
	border: 1px solid $line-strong;
	border-left: 2px solid $accent;
	padding: 10px 14px;
	flex-direction: column;
	gap: 8px;

	&.float-dock--row { flex-direction: row; align-items: center; }
	&.float-dock--quiet { border-left-color: $line-mid; }
	&.float-dock--focus { border-left-color: $accent; }
	&.float-dock--danger { border-left-color: $danger; }
}
_TMP_PRIMARY;
	padding: 10px 14px;
	flex-direction: column;
	gap: 8px;

	&.float-dock--row { flex-direction: row; align-items: center; }
	&.float-dock--quiet { border-left-color: $line-mid; }
	&.float-dock--focus { border-left-color: $accent; }
	&.float-dock--danger { border-left-color: $danger; }
}
dogecoin.ssoundboard / ui/components/inputglyph/inputglyph.razor.scss
Game game
@import "/ui/styles/_theme.scss";

.input-glyph {
	flex-direction: row;
	align-items: center;
	justify-content: center;

	img {
		width: 32px;
		height: 32px;
	}

	.input-glyph__fallback {
		padding: 3px 8px;
		background-color: rgba(255, 255, 255, 0.06);
		border: 1px solid $line-strong;
		color: $text;
		font-family: $font-mono;
		font-size: $fs-1;
		font-weight: 800;
		letter-spacing: 1px;
		text-transform: uppercase;
	}
}
dogecoin.ssoundboard / UI/Components/EmptyState/EmptyState.razor
Game game
@namespace Sunless.Libraries.UI
@using Sandbox
@using Sandbox.UI
@attribute [StyleSheet]
@inherits Panel

<root>
	<div class="empty">
		<div class="empty-icon">@Icon</div>
		<div class="empty-title">@Title</div>
		@if ( !string.IsNullOrEmpty( Sub ) )
		{
			<div class="empty-sub">@Sub</div>
		}
	</div>
</root>

@code
{
	public string Icon { get; set; } = "-- --";
	public string Title { get; set; } = "";
	public string Sub { get; set; } = "";

	protected override int BuildHash() => HashCode.Combine( Icon, Title, Sub );
}
dogecoin.ssoundboard / UI/Components/Icon/Icon.razor
Game game
@namespace Sunless.Libraries.UI
@using Sandbox
@using Sandbox.UI
@attribute [StyleSheet]
@inherits Panel

<root class="icon @SizeClass @ExtraClass">@Name</root>

@code
{
	public string Name { get; set; } = "";
	public IconSize Size { get; set; } = IconSize.Medium;
	public string ExtraClass { get; set; }

	string SizeClass => Size switch
	{
		IconSize.Small  => "icon-sm",
		IconSize.Large  => "icon-lg",
		IconSize.XLarge => "icon-xl",
		_               => "icon-md",
	};

	protected override int BuildHash() => HashCode.Combine( Name, Size, ExtraClass );
}
dogecoin.ssoundboard / UI/Components/Menu/MenuWidgetBuilders.cs
Game game
namespace Sunless.Libraries.UI;

using System;
using System.Collections.Generic;

public sealed class MenuLinkBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuLinkWidget _w;

	internal MenuLinkBuilder( MenuScreenBuilder screen, string label, string icon )
	{
		_screen = screen;
		_w = new MenuLinkWidget { Label = label, Icon = icon, Style = MenuLinkStyle.Row };
	}

	public MenuLinkBuilder WithStyle( MenuLinkStyle style )  { _w.Style = style; return this; }
	public MenuLinkBuilder WithTone( MenuTone tone )         { _w.Tone = tone; return this; }
	public MenuLinkBuilder WithHint( string hint )           { _w.Hint = hint; return this; }
	public MenuLinkBuilder WhenEnabled( Func<bool> enabled ) { _w.Enabled = enabled; return this; }
	public MenuLinkBuilder WithGoTo( string routeId )        { _w.RouteId = routeId; return this; }
	public MenuLinkBuilder WithAction( Action invoke )       { _w.Invoke = invoke; return this; }
	public MenuLinkBuilder Grows()                           { _w.Grow = true; return this; }

	public MenuScreenBuilder EndLink()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuToggleBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuToggleWidget _w;

	internal MenuToggleBuilder( MenuScreenBuilder screen, string label )
	{
		_screen = screen;
		_w = new MenuToggleWidget { Label = label };
	}

	public MenuToggleBuilder WithHint( string hint )        { _w.Hint = hint; return this; }
	public MenuToggleBuilder WithGetter( Func<bool> get )   { _w.Get = get; return this; }
	public MenuToggleBuilder WithSetter( Action<bool> set ) { _w.Set = set; return this; }

	public MenuToggleBuilder Bind( Func<bool> get, Action<bool> set )
	{
		_w.Get = get;
		_w.Set = set;
		return this;
	}

	public MenuScreenBuilder EndToggle()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuRangeBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuRangeWidget _w;

	internal MenuRangeBuilder( MenuScreenBuilder screen, string label )
	{
		_screen = screen;
		_w = new MenuRangeWidget { Label = label, Min = 0f, Max = 1f, Step = 0.01f, Format = "0.00" };
	}

	public MenuRangeBuilder WithHint( string hint )         { _w.Hint = hint; return this; }
	public MenuRangeBuilder WithRange( float min, float max ) { _w.Min = min; _w.Max = max; return this; }
	public MenuRangeBuilder WithStep( float step )            { _w.Step = step; return this; }
	public MenuRangeBuilder WithFormat( string format )       { _w.Format = format; return this; }

	public MenuRangeBuilder Bind( Func<float> get, Action<float> set )
	{
		_w.Get = get;
		_w.Set = set;
		return this;
	}

	public MenuScreenBuilder EndRange()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuSegmentedBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuSegmentedWidget _w;

	internal MenuSegmentedBuilder( MenuScreenBuilder screen, string label, string[] choices )
	{
		_screen = screen;
		_w = new MenuSegmentedWidget { Label = label, Choices = choices };
	}

	public MenuSegmentedBuilder WithHint( string hint ) { _w.Hint = hint; return this; }

	public MenuSegmentedBuilder Bind( Func<int> get, Action<int> set )
	{
		_w.Get = get;
		_w.Set = set;
		return this;
	}

	public MenuScreenBuilder EndSegmented()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuRadioBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuRadioWidget _w;
	readonly List<MenuRadioWidget.Option> _options = new();

	internal MenuRadioBuilder( MenuScreenBuilder screen, string label )
	{
		_screen = screen;
		_w = new MenuRadioWidget { Label = label };
	}

	public MenuRadioBuilder WithHint( string hint ) { _w.Hint = hint; return this; }

	public MenuRadioBuilder WithOption( string label, string icon = null, string hint = null )
	{
		_options.Add( new MenuRadioWidget.Option { Label = label, Icon = icon, Hint = hint } );
		return this;
	}

	public MenuRadioBuilder Bind( Func<int> get, Action<int> set )
	{
		_w.Get = get;
		_w.Set = set;
		return this;
	}

	public MenuScreenBuilder EndRadio()
	{
		_w.Options = _options;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuInfoBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuInfoWidget _w;

	internal MenuInfoBuilder( MenuScreenBuilder screen, string label )
	{
		_screen = screen;
		_w = new MenuInfoWidget { Label = label };
	}

	public MenuInfoBuilder WithIcon( string icon )         { _w.Icon = icon; return this; }
	public MenuInfoBuilder WithGetter( Func<string> get )  { _w.Get = get; return this; }
	public MenuInfoBuilder WithValue( string value )       { _w.Get = () => value; return this; }

	public MenuScreenBuilder EndInfo()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuTextFieldBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuTextFieldWidget _w;

	internal MenuTextFieldBuilder( MenuScreenBuilder screen, string label )
	{
		_screen = screen;
		_w = new MenuTextFieldWidget { Label = label };
	}

	public MenuTextFieldBuilder WithHint( string hint ) { _w.Hint = hint; return this; }

	public MenuTextFieldBuilder Bind( Func<string> get, Action<string> set )
	{
		_w.Get = get;
		_w.Set = set;
		return this;
	}

	public MenuScreenBuilder EndTextField()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

// === Composite widgets ===============================================

public sealed class MenuHeroCardBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuHeroCardWidget _w = new();

	internal MenuHeroCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuHeroCardBuilder WithTitle( string title )             { _w.Title = title; return this; }
	public MenuHeroCardBuilder WithSubtitle( string subtitle )       { _w.Subtitle = subtitle; return this; }
	public MenuHeroCardBuilder WithDescription( string description ) { _w.Description = description; return this; }
	public MenuHeroCardBuilder WithImage( string url )               { _w.Image = url; return this; }
	public MenuHeroCardBuilder WithIcon( string icon )               { _w.Icon = icon; return this; }

	public MenuScreenBuilder EndHeroCard()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuMapCardBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuMapCardWidget _w = new();

	internal MenuMapCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuMapCardBuilder WithTitle( string title )             { _w.Title = title; return this; }
	public MenuMapCardBuilder WithDescription( string description ) { _w.Description = description; return this; }
	public MenuMapCardBuilder WithImage( string url )               { _w.Image = url; return this; }

	public MenuMapCardBuilder WithChip( string label, string icon = null, MenuTone tone = MenuTone.Confirm )
	{
		_w.ChipLabel = label;
		_w.ChipIcon = icon;
		_w.ChipTone = tone;
		return this;
	}

	public MenuMapCardBuilder WithAction( string label, string icon, Action invoke, string routeId = null )
	{
		_w.ActionLabel = label;
		_w.ActionIcon = icon;
		_w.ActionInvoke = invoke;
		_w.ActionRouteId = routeId;
		return this;
	}

	public MenuScreenBuilder EndMapCard()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuMapGridBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuMapGridWidget _w = new();
	readonly List<MenuMapGridWidget.Card> _cards = new();

	internal MenuMapGridBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuMapGridBuilder WithActive( Func<int> get )  { _w.Active = get; return this; }
	public MenuMapGridBuilder OnPick( Action<int> set )    { _w.Pick = set; return this; }

	public MenuMapCardCardBuilder WithCard( string title )
		=> new( this, title );

	internal void AddCard( MenuMapGridWidget.Card card ) => _cards.Add( card );

	bool _closed;

	public MenuScreenBuilder EndMapGrid()
	{
		if ( _closed ) return _screen;
		_closed = true;
		_w.Cards = _cards;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuMapCardCardBuilder
{
	readonly MenuMapGridBuilder _grid;
	readonly MenuMapGridWidget.Card _c;

	internal MenuMapCardCardBuilder( MenuMapGridBuilder grid, string title )
	{
		_grid = grid;
		_c = new MenuMapGridWidget.Card { Title = title };
	}

	public MenuMapCardCardBuilder WithDescription( string text ) { _c.Description = text; return this; }
	public MenuMapCardCardBuilder WithImage( string url )        { _c.Image = url; return this; }
	public MenuMapCardCardBuilder WithBadge( string label, MenuTone tone = MenuTone.Confirm )
	{
		_c.BadgeLabel = label;
		_c.BadgeTone = tone;
		return this;
	}

	public MenuMapCardCardBuilder ComingSoon( bool v = true ) { _c.ComingSoon = v; return this; }
	public MenuMapCardCardBuilder Locked( bool v = true )     { _c.Locked = v; return this; }

	public MenuMapGridBuilder EndCard()
	{
		_grid.AddCard( _c );
		return _grid;
	}
}

public sealed class MenuTileGridBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuTileGridWidget _w = new();
	readonly List<MenuTileGridWidget.Item> _items = new();

	internal MenuTileGridBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuTileGridBuilder WithTile( string label, string color = null, string image = null )
	{
		_items.Add( new MenuTileGridWidget.Item { Label = label, Color = color, Image = image } );
		return this;
	}

	public MenuTileGridBuilder WithActive( Func<int> get ) { _w.Active = get; return this; }
	public MenuTileGridBuilder OnPick( Action<int> set )   { _w.Pick = set; return this; }

	public MenuScreenBuilder EndTileGrid()
	{
		_w.Items = _items;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuPlayerListBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuPlayerListWidget _w = new();
	readonly List<MenuPlayerListWidget.Player> _players = new();

	internal MenuPlayerListBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuPlayerListBuilder WithPlayer( string name, bool ready = false, bool host = false )
	{
		_players.Add( new MenuPlayerListWidget.Player { Name = name, Ready = ready, Host = host } );
		return this;
	}

	public MenuScreenBuilder EndPlayerList()
	{
		_w.Players = _players;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuKeyBindingsBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuKeyBindingsWidget _w = new();
	readonly List<MenuKeyBindingsWidget.Binding> _bindings = new();

	internal MenuKeyBindingsBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuKeyBindingsBuilder WithBinding( string action, params string[] keys )
	{
		_bindings.Add( new MenuKeyBindingsWidget.Binding { Action = action, Keys = keys } );
		return this;
	}

	public MenuScreenBuilder EndKeyBindings()
	{
		_w.Bindings = _bindings;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuPromptCardBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuPromptCardWidget _w = new() { Verb = "Press" };

	internal MenuPromptCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuPromptCardBuilder WithVerb( string verb )      { _w.Verb = verb; return this; }
	public MenuPromptCardBuilder WithPrompt( string prompt )  { _w.Prompt = prompt; return this; }
	public MenuPromptCardBuilder WithGlyph( string ligature ) { _w.Glyph = ligature; return this; }
	public MenuPromptCardBuilder WithCost( int cost )         { _w.Cost = cost; return this; }
	public MenuPromptCardBuilder WhenDisabled( Func<bool> d ) { _w.Disabled = d; return this; }
	public MenuPromptCardBuilder WithHoldProgress( Func<float> p ) { _w.Progress = p; return this; }

	public MenuScreenBuilder EndPromptCard()
	{
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuActionRowBuilder
{
	readonly MenuScreenBuilder _screen;
	readonly MenuActionRowWidget _w = new();
	readonly List<MenuLinkWidget> _links = new();

	internal MenuActionRowBuilder( MenuScreenBuilder screen ) { _screen = screen; }

	public MenuActionRowLinkBuilder WithLink( string label, string icon = null )
		=> new( this, label, icon );

	internal void AddLink( MenuLinkWidget link ) => _links.Add( link );

	public MenuScreenBuilder EndActionRow()
	{
		_w.Links = _links;
		_screen.AddWidget( _w );
		return _screen;
	}
}

public sealed class MenuActionRowLinkBuilder
{
	readonly MenuActionRowBuilder _row;
	readonly MenuLinkWidget _w;

	internal MenuActionRowLinkBuilder( MenuActionRowBuilder row, string label, string icon )
	{
		_row = row;
		_w = new MenuLinkWidget { Label = label, Icon = icon, Style = MenuLinkStyle.Button };
	}

	public MenuActionRowLinkBuilder WithTone( MenuTone tone )         { _w.Tone = tone; return this; }
	public MenuActionRowLinkBuilder WithGoTo( string routeId )        { _w.RouteId = routeId; return this; }
	public MenuActionRowLinkBuilder WithAction( Action invoke )       { _w.Invoke = invoke; return this; }
	public MenuActionRowLinkBuilder WhenEnabled( Func<bool> enabled ) { _w.Enabled = enabled; return this; }
	public MenuActionRowLinkBuilder Grows()                           { _w.Grow = true; return this; }

	public MenuActionRowBuilder EndLink()
	{
		_row.AddLink( _w );
		return _row;
	}
}
dogecoin.ssoundboard / UI/Components/Menu/PromptCard.razor
Game game
@namespace Sunless.Libraries.UI
@using Sandbox
@using Sandbox.UI
@inherits Panel
@attribute [StyleSheet]

<root class="@(IsDisabled ? "disabled" : "")">
	<div class="prompt-frame">
		<div class="prompt-vignette"></div>

		<div class="prompt-content">
			<div class="prompt-glyph">
				@if ( !string.IsNullOrEmpty( Glyph ) )
				{
					<div class="kbd">@Glyph</div>
				}
			</div>

			<div class="prompt-text">
				<div class="prompt-verb">@EffectiveVerb</div>
				<div class="prompt-label">@Prompt</div>
			</div>

			@if ( Cost > 0 )
			{
				<div class="prompt-cost">
					<div class="prompt-cost-value">@Cost</div>
					<div class="prompt-cost-unit">PTS</div>
				</div>
			}
		</div>

		<div class="prompt-rule">
			<div class="prompt-rule-fill" @ref="ruleFill"></div>
		</div>
	</div>
</root>

@code
{
	public string Verb { get; set; } = "Press";
	public string Prompt { get; set; } = "";
	public string Glyph { get; set; } = "E";
	public int Cost { get; set; }
	public bool IsDisabled { get; set; }
	public Func<float> HoldProgressFn { get; set; }

	Panel ruleFill { get; set; }

	string EffectiveVerb => IsDisabled && Cost > 0 ? "Not enough points" : Verb;

	public override void Tick()
	{
		if ( ruleFill is null ) return;

		var p = HoldProgressFn?.Invoke() ?? 0f;
		if ( p < 0f ) p = 0f;
		else if ( p > 1f ) p = 1f;

		ruleFill.Style.Opacity = p > 0f ? 1f : 0f;
		ruleFill.Style.Width = Length.Percent( p * 100f );
	}

	protected override int BuildHash() => HashCode.Combine( Verb, Prompt, Glyph, Cost, IsDisabled );
}
dogecoin.ssoundboard / SoundBoardNet.cs
Game game
using System;

namespace SSoundboard;

/// <summary>
/// Network relay for co-op pad presses. Host owns one scene object; clients
/// call Rpc.Host then host rebroadcasts so every machine hears the chaos.
/// </summary>
public sealed class SoundBoardNet : Component
{
	public static SoundBoardNet Instance { get; private set; }

	protected override void OnEnabled()
	{
		Instance = this;
	}

	protected override void OnDisabled()
	{
		if ( Instance == this )
			Instance = null;
	}

	protected override void OnDestroy()
	{
		if ( Instance == this )
			Instance = null;
	}

	/// <summary>Host creates a networked singleton when a multiplayer session is live.</summary>
	public static void EnsureHostRelay()
	{
		try
		{
			if ( !Networking.IsActive )
				return;

			if ( Instance.IsValid() )
				return;

			if ( !Networking.IsHost )
				return;

			var go = new GameObject( true, "SoundBoardNet" );
			go.Components.Create<SoundBoardNet>();
			go.NetworkSpawn();
			Log.Info( "[S&Soundboard] CO-OP network relay spawned" );
		}
		catch ( Exception e )
		{
			Log.Warning( $"[S&Soundboard] CO-OP relay spawn failed: {e.Message}" );
		}
	}

	/// <summary>Local player pressed a pad while CO-OP is on — share with the lobby.</summary>
	public static void SharePadPress( SoundPad pad )
	{
		if ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )
			return;

		if ( !SoundBoardCoop.Enabled )
			return;

		try
		{
			if ( !Networking.IsActive )
				return;

			EnsureHostRelay();

			var net = Instance;
			if ( !net.IsValid() )
				return;

			// Calling Rpc on a non-networked object throws ("must be networked").
			try
			{
				if ( net.GameObject is null || !net.GameObject.Network.Active )
					return;
			}
			catch
			{
				return;
			}

			net.RequestPad(
				pad.SoundId,
				pad.Label ?? "",
				pad.Color ?? "#ffffff",
				pad.Volume <= 0f ? 1f : pad.Volume );
		}
		catch ( Exception e )
		{
			// Never let lobby sync break local button presses.
			Log.Warning( $"[S&Soundboard] CO-OP share failed: {e.GetType().Name}: {e.Message}" );
		}
	}

	[Rpc.Host]
	void RequestPad( string soundId, string label, string color, float volume )
	{
		try
		{
			if ( string.IsNullOrWhiteSpace( soundId ) )
				return;

			var caller = Rpc.Caller;
			var name = ResolveDisplayName( caller );
			var steamId = ResolveSteamId( caller );

			// Host rebroadcasts to everyone (including the original presser).
			ReceivePad( soundId, label ?? "", color ?? "#ffffff", volume, name, steamId );
		}
		catch ( Exception e )
		{
			Log.Warning( $"[S&Soundboard] CO-OP RequestPad failed: {e.GetType().Name}: {e.Message}" );
		}
	}

	static string ResolveDisplayName( Connection caller )
	{
		if ( caller is null )
			return "Player";

		try
		{
			if ( !string.IsNullOrWhiteSpace( caller.DisplayName ) )
				return caller.DisplayName;
		}
		catch { /* ignore */ }

		try
		{
			if ( !string.IsNullOrWhiteSpace( caller.Name ) )
				return caller.Name;
		}
		catch { /* ignore */ }

		return "Player";
	}

	static long ResolveSteamId( Connection caller )
	{
		if ( caller is null )
			return 0;

		// Prefer string parse — SteamId / Friend.Id shapes vary; never throw on cast.
		try
		{
			var id = ParseSteamId( caller.FriendInfo.Id );
			if ( id != 0 )
				return id;
		}
		catch { /* ignore */ }

		try
		{
			var id = ParseSteamId( caller.SteamId );
			if ( id != 0 )
				return id;
		}
		catch { /* ignore */ }

		try
		{
			return ParseSteamId( caller.OwnerSteamId );
		}
		catch
		{
			return 0;
		}
	}

	static long ParseSteamId( object value )
	{
		if ( value is null )
			return 0;

		try
		{
			switch ( value )
			{
				case long l:
					return l;
				case ulong ul:
					return unchecked( (long)ul );
				case int i:
					return i;
				case uint ui:
					return ui;
			}
		}
		catch { /* fall through */ }

		try
		{
			var s = value.ToString();
			if ( string.IsNullOrWhiteSpace( s ) )
				return 0;
			// SteamId.ToString() is typically the numeric account id.
			if ( long.TryParse( s, out var parsed ) )
				return parsed;
		}
		catch { /* ignore */ }

		return 0;
	}

	[Rpc.Broadcast( NetFlags.HostOnly | NetFlags.Reliable )]
	void ReceivePad( string soundId, string label, string color, float volume, string playerName, long steamId )
	{
		SoundBoardCoop.OnNetworkPad( soundId, label, color, volume, playerName, steamId );
	}
}
dogecoin.ssoundboard / SoundPreloadService.cs
Game game
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SSoundboard;

/// <summary>
/// On-demand sound loading. Prefer TryGet (already compiled); LoadAsync only on click.
/// </summary>
public static class SoundPreloadService
{
	enum LoadState { Pending, Loading, Ready, Failed }

	static readonly Dictionary<string, LoadState> _states = new( StringComparer.OrdinalIgnoreCase );
	static readonly List<string> _priority = new();
	static readonly Dictionary<string, SoundPad> _pendingPlays = new( StringComparer.OrdinalIgnoreCase );

	static int _loaded;
	static int _registered;
	static int _activeLoads;
	static bool _initialized;
	static bool _startupComplete;

	const int MaxConcurrentLoads = 2;
	const int StartupWarmCap = 32;

	public static event Action Changed;

	public static int LoadedCount => _loaded;
	public static bool IsInitialized => _initialized;
	public static bool IsStartupComplete => _startupComplete;
	/// <summary>Bumps when pending-play set changes — cheap UI rebuild signal.</summary>
	public static int PendingRevision { get; private set; }

	public static void Initialize()
	{
		if ( _initialized )
			return;

		_initialized = true;

		var warmed = 0;
		foreach ( var soundId in SoundBoardData.StartupSoundIds )
		{
			if ( string.IsNullOrWhiteSpace( soundId ) )
				continue;

			if ( TryMarkReadyIfPresent( Normalize( soundId ) ) )
				warmed++;

			if ( warmed >= StartupWarmCap )
				break;
		}

		_startupComplete = true;
		Changed?.Invoke();
	}

	static void EnsureInitialized()
	{
		if ( !_initialized )
			Initialize();
	}

	public static bool IsReady( string soundId )
	{
		if ( string.IsNullOrWhiteSpace( soundId ) )
			return false;

		EnsureInitialized();
		var key = Normalize( soundId );

		if ( _states.TryGetValue( key, out var state ) )
		{
			if ( state == LoadState.Ready )
				return true;
			if ( state is LoadState.Failed or LoadState.Loading )
				return false;
		}

		return TryMarkReadyIfPresent( key );
	}

	public static void BumpPriority( string soundId )
	{
		if ( string.IsNullOrWhiteSpace( soundId ) )
			return;

		EnsureInitialized();
		var key = Normalize( soundId );

		if ( IsReady( key ) )
			return;

		if ( _states.TryGetValue( key, out var state ) )
		{
			if ( state is LoadState.Loading or LoadState.Ready )
				return;

			if ( state == LoadState.Failed )
				_states[key] = LoadState.Pending;
		}
		else
		{
			_states[key] = LoadState.Pending;
			_registered++;
		}

		_priority.Remove( key );
		_priority.Insert( 0, key );
		Changed?.Invoke();
	}

	public static void QueuePlayWhenReady( SoundPad pad )
	{
		if ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )
			return;

		var key = Normalize( pad.SoundId );
		_pendingPlays[key] = pad;
		PendingRevision++;
		BumpPriority( key );
	}

	public static bool TryTakePendingPlay( string soundId, out SoundPad pad )
	{
		var removed = _pendingPlays.Remove( Normalize( soundId ), out pad );
		if ( removed )
			PendingRevision++;
		return removed;
	}

	public static bool IsPendingPlay( string soundId )
	{
		return _pendingPlays.ContainsKey( Normalize( soundId ) );
	}

	public static void Tick()
	{
		EnsureInitialized();
		TryPumpLoads();
	}

	static void TryPumpLoads()
	{
		while ( _activeLoads < MaxConcurrentLoads && TryDequeue( out var soundId ) )
		{
			if ( TryMarkReadyIfPresent( soundId ) )
			{
				SoundBoardManager.OnSoundReady( soundId );
				continue;
			}

			_activeLoads++;
			_states[soundId] = LoadState.Loading;
			_ = LoadOneAsync( soundId );
		}
	}

	static bool TryDequeue( out string soundId )
	{
		while ( _priority.Count > 0 )
		{
			var next = _priority[0];
			_priority.RemoveAt( 0 );

			if ( !_states.TryGetValue( next, out var st ) || st != LoadState.Pending )
				continue;

			soundId = next;
			return true;
		}

		soundId = null;
		return false;
	}

	static async Task LoadOneAsync( string soundId )
	{
		var path = ToSoundPath( soundId );

		try
		{
			if ( ResourceLibrary.TryGet<SoundEvent>( path, out _ ) )
			{
				MarkReady( soundId );
				SoundBoardManager.OnSoundReady( soundId );
				return;
			}

			var loaded = await ResourceLibrary.LoadAsync<SoundEvent>( path );
			if ( loaded is null )
			{
				MarkFailed( soundId );
				return;
			}

			try { Sound.Preload( path ); }
			catch { /* playable without preload */ }

			MarkReady( soundId );
			SoundBoardManager.OnSoundReady( soundId );
		}
		catch ( Exception e )
		{
			Log.Warning( $"[S&Soundboard] Sound load skipped: {path} ({e.GetType().Name})" );
			MarkFailed( soundId );
		}
		finally
		{
			_activeLoads = Math.Max( 0, _activeLoads - 1 );
		}
	}

	static bool TryMarkReadyIfPresent( string key )
	{
		try
		{
			if ( !ResourceLibrary.TryGet<SoundEvent>( ToSoundPath( key ), out _ ) )
				return false;
		}
		catch
		{
			return false;
		}

		MarkReady( key );
		return true;
	}

	static void MarkReady( string key )
	{
		var already = _states.TryGetValue( key, out var prev ) && prev == LoadState.Ready;
		_states[key] = LoadState.Ready;
		if ( !already )
			_loaded++;
		Changed?.Invoke();
	}

	static void MarkFailed( string key )
	{
		_states[key] = LoadState.Failed;
		if ( _pendingPlays.Remove( key ) )
			PendingRevision++;
		Changed?.Invoke();
	}

	static string Normalize( string soundId )
	{
		if ( string.IsNullOrWhiteSpace( soundId ) )
			return soundId;

		soundId = soundId.Replace( '\\', '/' ).Trim();
		return soundId.EndsWith( ".sound", StringComparison.OrdinalIgnoreCase )
			? soundId[..^6]
			: soundId;
	}

	static string ToSoundPath( string soundId ) => $"{Normalize( soundId )}.sound";
}
Debug: View Raw JSON Response
{
    "TotalCount": 118,
    "Files": [
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/controls/color/colorhuecontrol.cs.scss",
            "FileName": "colorhuecontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "ColorHueControl\r\n{\r\n\tgap: 0.5rem;\r\n\tflex-grow: 1;\r\n\tpointer-events: all;\r\n\tbackground: linear-gradient( to right, red, yellow, lime, cyan, blue, magenta, red );\r\n\tborder-radius: 4px;\r\n\tpadding: 2px;\r\n\theight: 12px;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tmargin: 10px 0;\r\n\tborder: 1px solid #333;\r\n\r\n\t&:hover\r\n\t{\r\n\t\tborder: 1px solid #08f;\r\n\t}\r\n\r\n\t&:active\r\n\t{\r\n\t\tborder: 1px solid #fff;\r\n\t}\r\n\r\n\t.handle\r\n\t{\r\n\t\ttop: -5px;\r\n\t\tbottom: -5px;\r\n\t\taspect-ratio: 1;\r\n\t\tborder-radius: 100px;\r\n\t\tborder: 2px solid #444;\r\n\t\tposition: absolute;\r\n\t\tbackground-color: white;\r\n\t\tbox-shadow: 2px 2px 16px #000a;\r\n\t\ttransform: translateX( -50% );\r\n\t\tpointer-events: none;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "styles/rootpanel.scss",
            "FileName": "rootpanel.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "\r\n\r\nRootPanel\r\n{\r\n\tfont-family: Roboto;\r\n\tcolor: white;\r\n}\r\n\r\nIconPanel, i\r\n{\r\n\tfont-family: Material Icons;\r\n\tbackground-position: center;\r\n\tbackground-size: contain;\r\n}\r\n\r\n.tooltip\r\n{\r\n\tbackground-color: rgba( black, 0.8 );\r\n\tborder: 1px solid rgba( #555, 0.1 );\r\n\tpadding: 10px;\r\n\tborder-radius: 4px;\r\n\tcolor: white;\r\n\tfont-family: Inter;\r\n\tfont-weight: bold;\r\n\ttransition: opacity 0.09s ease;\r\n\topacity: 1;\r\n\tmargin: 0px;\r\n\r\n\t&:intro, &:outro\r\n\t{\r\n\t\topacity: 0;\r\n\t}\r\n}\r\n\r\n.textentry\r\n{\r\n\tcursor: text;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\twhite-space: nowrap;\r\n\toverflow: hidden;\r\n\tflex-direction: row;\r\n\tposition: relative;\r\n\tpadding: 10px;\r\n\r\n\t&.disabled\r\n\t{\r\n\t\tcursor: default;\r\n\t}\r\n\r\n\t&.is-multiline\r\n\t{\r\n\t\talign-items: flex-start;\r\n\t\twhite-space: normal;\r\n\t}\r\n\r\n\t.content-label,\r\n\t.placeholder\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t}\r\n\r\n\t.placeholder\r\n\t{\r\n\t\topacity: 0.2;\r\n\t\tpointer-events: none;\r\n\t}\r\n\r\n\t.iconpanel\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tright: 0;\r\n\t\tpadding-right: 10px;\r\n\t\tpointer-events: none;\r\n\r\n\t\t&.clearbutton\r\n\t\t{\r\n\t\t\tcolor: #fff;\r\n\t\t\tpointer-events: all;\r\n\t\t\tcursor: pointer;\r\n\t\t\topacity: 0.1;\r\n\r\n\t\t\t&:hover\r\n\t\t\t{\r\n\t\t\t\topacity: 0.5;\r\n\t\t\t}\r\n\r\n\t\t\t&:active\r\n\t\t\t{\r\n\t\t\t\topacity: 1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t&:empty\r\n\t{\r\n\t\t.clearbutton\r\n\t\t{\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\r\n\t&.has-icon\r\n\t{\r\n\t\tpadding-right: 30px;\r\n\t}\r\n\r\n\t.content-label,\r\n\t.prefix-label,\r\n\t.suffix-label,\r\n\t.placeholder\r\n\t{\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.content-label\r\n\t{\r\n\t\torder: 1;\r\n\t}\r\n\r\n\t.suffix-label\r\n\t{\r\n\t\torder: 2;\r\n\t}\r\n\r\n\t.prefix-label,\r\n\t.suffix-label\r\n\t{\r\n\t\tflex-shrink: 0;\r\n\t\topacity: 0.5;\r\n\t}\r\n\r\n\t&.invalid\r\n\t{\r\n\t\tbox-shadow: 0px 0px 1px 2px #f8356b;\r\n\t}\r\n}\r\n\r\n.popup-panel\r\n{\r\n\tbackground-color: #1b202c;\r\n\tflex-direction: column;\r\n\tcolor: #8e9ec2;\r\n\ttransition: all 0.1s ease-out;\r\n\tposition: absolute;\r\n\tz-index: 2000;\r\n\tmin-height: 20px;\r\n\tmin-width: 10px;\r\n\toverflow: scroll;\r\n\tpointer-events: all;\r\n\tbox-shadow: 3px 3px 64px #000;\r\n\tborder-radius: 4px;\r\n\r\n\t&.medium\r\n\t{\r\n\t\tmax-width: 300px;\r\n\t}\r\n\r\n\t&.center-horizontal\r\n\t{\r\n\t\ttransform-origin: top center;\r\n\t}\r\n\r\n\t&.below-center\r\n\t{\r\n\t\ttransform: translateX( -50% );\r\n\t\ttransform-origin: 0% 0%;\r\n\t}\r\n\r\n\t&.above-left\r\n\t{\r\n\t\ttransform-origin: 0% 100%;\r\n\t}\r\n\r\n\t&.above-center\r\n\t{\r\n\t\ttransform: translateX( -50% );\r\n\t\ttransform-origin: 0% 100%;\r\n\t}\r\n\r\n\t&.left\r\n\t{\r\n\t\ttransform-origin: 100% 50%;\r\n\t\ttransform: translate( 00% -50% );\r\n\t}\r\n\r\n\t&.right\r\n\t{\r\n\t\ttransform-origin: 0 50%;\r\n\t\ttransform: translate( 0% -50% );\r\n\t}\r\n\r\n\t&:intro\r\n\t{\r\n\t\ttransform: scale( 0 );\r\n\t\tpointer-events: none;\r\n\t\topacity: 0;\r\n\t}\r\n\r\n\t&:outro\r\n\t{\r\n\t\topacity: 0;\r\n\t\ttransition: all 0.1s ease-in;\r\n\t\tmargin-top: 50px;\r\n\t\tpointer-events: none;\r\n\t}\r\n\r\n\t&.below-stretch\r\n\t{\r\n\t\ttransform-origin: 0% 0%;\r\n\r\n\t\t&:intro\r\n\t\t{\r\n\t\t\ttransform: scaleY( 0.1 );\r\n\t\t}\r\n\t}\r\n\r\n\t> .canvas\r\n\t{\r\n\t\tflex-direction: column;\r\n\t}\r\n\r\n\t.information\r\n\t{\r\n\t\tpadding: 16px;\r\n\t\tfont-size: 14px;\r\n\t\topacity: 0.5;\r\n\t}\r\n\r\n\tbutton\r\n\t{\r\n\t\tpadding: 2px 8px;\r\n\t\tcursor: pointer;\r\n\t\tfont-family: Poppins;\r\n\t\tfont-weight: 600;\r\n\t\tflex-shrink: 0;\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\tbackground-color: #08f;\r\n\t\t\tcolor: #fff;\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/anchorpad/anchorpad.razor.scss",
            "FileName": "anchorpad.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n\n.anchor-pad {\n\twidth: 130px;\n\theight: 90px;\n\tbackground-color: $bg-2;\n\tborder: 1px solid $line-strong;\n\tpadding: 6px;\n\tposition: relative;\n\n\t.anchor-cell {\n\t\tpointer-events: all;\n\t\tbackground-color: rgba(255,255,255,0.04);\n\t\tborder: 1px solid $line-strong;\n\t\tcursor: pointer;\n\t\tposition: absolute;\n\t\twidth: 34px;\n\t\theight: 22px;\n\t\ttransition: background-color 0.1s ease, border-color 0.1s ease;\n\n\t\t&:hover { background-color: rgba(255,255,255,0.1); border-color: $accent; }\n\n\t\t&.active {\n\t\t\tbackground-color: $accent;\n\t\t\tborder-color: $accent;\n\t\t}\n\n\t\t&.anchor-topleft     { left: 6px;  top: 6px; }\n\t\t&.anchor-topright    { right: 6px; top: 6px; }\n\t\t&.anchor-bottomleft  { left: 6px;  bottom: 6px; }\n\t\t&.anchor-bottomright { right: 6px; bottom: 6px; }\n\t\t&.anchor-center      { left: 50%; top: 50%; transform: translate(-50%, -50%); }\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/hintbox/hintbox.scss",
            "FileName": "hintbox.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n\n.hint-box {\n\tpadding: 14px 16px;\n\tbackground-color: rgba(98, 200, 255, 0.07);\n\tborder-left: 3px solid $info;\n\tcolor: $text-dim;\n\tfont-size: $fs-3;\n\tfont-weight: 500;\n\tletter-spacing: 0.2px;\n\tcode { color: $info; font-family: $font-mono; font-weight: 700; }\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/tile/tile.scss",
            "FileName": "tile.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n\n.tile {\n\tflex-direction: column;\n\twidth: 140px;\n\tpadding: 0;\n\tbackground-color: $bg-2;\n\tborder: 1px solid $line-strong;\n\toverflow: hidden;\n\tcursor: pointer;\n\ttransition: border-color 0.1s ease;\n\n\t.tile-swatch {\n\t\twidth: 100%;\n\t\theight: 80px;\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tbackground-color: #1a1f2e;\n\t\tbackground-size: cover;\n\t\tbackground-position: center;\n\t}\n\n\t.tile-thumb {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tobject-fit: contain;\n\t}\n\n\t.tile-placeholder {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.tile-vignette {\n\t\tposition: absolute;\n\t\tleft: 0; top: 0; right: 0; bottom: 0;\n\t\tbackground-color: rgba(0,0,0,0.25);\n\t\tpointer-events: none;\n\t}\n\n\t.tile-label {\n\t\theight: 42px;\n\t\tpadding: 8px 10px;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: $fs-1;\n\t\tfont-weight: 700;\n\t\tcolor: $text-dim;\n\t\ttext-align: center;\n\t\tfont-family: $font-mono;\n\t\tletter-spacing: 1px;\n\t\ttext-transform: uppercase;\n\t\tborder-top: 1px solid $line;\n\t}\n\n\t&:hover {\n\t\tborder-color: $accent;\n\t\t.tile-label { color: $text; }\n\t}\n\t&.active {\n\t\tborder-color: $accent;\n\t\t.tile-label { color: $text; background-color: $accent-soft; font-weight: 800; }\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/vector3field/vector3field.razor.scss",
            "FileName": "vector3field.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n@import \"/ui/components/field/field.scss\";\n\n.v3-field {\n\t.v3-row {\n\t\tflex-direction: row;\n\t\tgap: 6px;\n\t\talign-items: stretch;\n\t\twidth: 100%;\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/packageflairbar.razor.scss",
            "FileName": "packageflairbar.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "PackageFlairBar\r\n{\r\n\tposition: absolute;\r\n\ttop: 6px;\r\n\tleft: 6px;\r\n\tz-index: 2;\r\n\tflex-direction: row;\r\n\tgap: 5px;\r\n\tpointer-events: none;\r\n\r\n\t.flair\r\n\t{\r\n\t\tposition: relative;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\twidth: 22px;\r\n\t\theight: 22px;\r\n\t\tpointer-events: all;\r\n\t\tborder-radius: 4px;\r\n\t\tcolor: white;\r\n\t\tborder: 1px solid rgba( white, 0.1 );\r\n\t\toutline: 1px solid #0006;\r\n\t\topacity: 0.7;\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\t\t}\r\n\r\n\t\ticon\r\n\t\t{\r\n\t\t\tfont-size: 15px;\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "soundboardpanel.razor.scss",
            "FileName": "soundboardpanel.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/UI/Styles/_Theme.scss\";\n@import \"/UI/Styles/_Fonts.scss\";\n@import \"/UI/Components/Tile/Tile.scss\";\n@import \"/UI/Components/Button/Button.scss\";\n\nSoundBoardPanel\n{\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tpointer-events: all;\n\toverflow: hidden;\n\n\t.sb\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tflex-direction: column;\n\t\tbackground-color: $bg-0;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-shell\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tmin-height: 0;\n\t\tpadding: 20px 24px 18px;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-topbar\n\t{\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tgap: 16px;\n\t\theight: 64px;\n\t\tmin-height: 64px;\n\t\tmax-height: 64px;\n\t\tmargin-bottom: 14px;\n\t\tpadding-bottom: 14px;\n\t\tborder-bottom: 1px solid $line;\n\t\tflex-shrink: 0;\n\t\tflex-wrap: nowrap;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-brand-block\n\t{\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 12px;\n\t\tmin-width: 0;\n\t\tflex-grow: 1;\n\t}\n\n\t.sb-status-led\n\t{\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line;\n\t\tflex-shrink: 0;\n\n\t\t&.is-live\n\t\t{\n\t\t\tbackground-color: $success;\n\t\t\tborder-color: $success-line;\n\t\t}\n\t}\n\n\t.sb-brand-copy\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 2px;\n\t\tmin-width: 0;\n\t}\n\n\t.sb-eyebrow\n\t{\n\t\tfont-size: $fs-1;\n\t\tfont-weight: 700;\n\t\tletter-spacing: $ls-xwide;\n\t\tcolor: $text-faint;\n\t\tfont-family: $font-mono;\n\t}\n\n\t.sb-title\n\t{\n\t\tfont-size: $fs-5;\n\t\tfont-weight: 800;\n\t\tcolor: $text;\n\t\tfont-family: $font-primary;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-voice-chip\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\tgap: 6px;\n\t\twidth: 128px;\n\t\theight: 52px;\n\t\tmin-width: 128px;\n\t\tmax-width: 128px;\n\t\tpadding: 8px 12px;\n\t\tborder-radius: 4px;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line-strong;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-voice-chip-head\n\t{\n\t\tdisplay: flex;\n\t\talign-items: baseline;\n\t\tjustify-content: space-between;\n\t\tgap: 8px;\n\t}\n\n\t.sb-voice-chip-label\n\t{\n\t\tfont-size: $fs-1;\n\t\tfont-weight: 700;\n\t\tletter-spacing: $ls-wide;\n\t\tcolor: $text-faint;\n\t\tfont-family: $font-mono;\n\t}\n\n\t.sb-voice-chip-value\n\t{\n\t\tfont-size: $fs-4;\n\t\tfont-weight: 800;\n\t\tline-height: 1;\n\t\tcolor: $accent;\n\t\tfont-family: $font-mono;\n\t}\n\n\t.sb-voice-meter-stack\n\t{\n\t\twidth: 100%;\n\t\theight: 18px;\n\t\tmin-height: 18px;\n\t\tmax-height: 18px;\n\t\tflex-direction: column;\n\t\tjustify-content: flex-end;\n\t\tgap: 2px;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-voice-meter-tier\n\t{\n\t\twidth: 100%;\n\t\theight: 3px;\n\t\tmin-height: 3px;\n\t\tmax-height: 3px;\n\t\tborder-radius: 999px;\n\t\tbackground-color: $bg-inset;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-voice-meter-fill\n\t{\n\t\theight: 100%;\n\t\tborder-radius: 999px;\n\t\ttransition: width 0.16s ease;\n\t}\n\n\t.sb-tabs-bar\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tgap: 8px;\n\t\twidth: 100%;\n\t\theight: 48px;\n\t\tmargin-bottom: 10px;\n\t\tpointer-events: all;\n\t}\n\n\t.sb-search-bar\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tgap: 10px;\n\t\twidth: 100%;\n\t\theight: 40px;\n\t\tmargin-bottom: 12px;\n\t\tpadding: 0 12px;\n\t\tborder-radius: 8px;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line-strong;\n\t\tpointer-events: all;\n\t}\n\n\t.sb-search-icon\n\t{\n\t\tflex-shrink: 0;\n\t\tfont-size: 16px;\n\t\tfont-weight: 700;\n\t\tcolor: $text-faint;\n\t\tline-height: 1;\n\t}\n\n\t.sb-search-input\n\t{\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tmin-width: 0;\n\t\theight: 36px;\n\t\tmin-height: 36px;\n\t\tpadding: 0 4px;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\tcolor: $text;\n\t\tfont-size: $fs-2;\n\t\tfont-weight: 600;\n\t\tfont-family: $font-mono;\n\t\tletter-spacing: 0.04em;\n\t}\n\n\t.sb-search-clear\n\t{\n\t\tdisplay: flex;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 28px;\n\t\theight: 28px;\n\t\tborder-radius: 6px;\n\t\tbackground-color: $bg-inset;\n\t\tborder: 1px solid $line;\n\t\tcolor: $text-dim;\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\n\t\t&:hover\n\t\t{\n\t\t\tborder-color: $accent;\n\t\t\tcolor: $accent;\n\t\t}\n\n\t\t&:active\n\t\t{\n\t\t\tsound-in: \"ui.button.press\";\n\t\t\ttransform: scale(0.96);\n\t\t}\n\t}\n\n\t.sb-search-count\n\t{\n\t\tflex-shrink: 0;\n\t\tmin-width: 36px;\n\t\ttext-align: right;\n\t\tfont-size: $fs-1;\n\t\tfont-weight: 700;\n\t\tletter-spacing: $ls-wide;\n\t\tcolor: $text-faint;\n\t\tfont-family: $font-mono;\n\t\twhite-space: nowrap;\n\t}\n\n\t.sb-empty\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tgap: 8px;\n\t\twidth: 100%;\n\t\tmin-height: 180px;\n\t\tpadding: 32px 16px;\n\t\tpointer-events: none;\n\t}\n\n\t.sb-empty-title\n\t{\n\t\tfont-size: $fs-4;\n\t\tfont-weight: 800;\n\t\tcolor: $text-dim;\n\t\tfont-family: $font-mono;\n\t\tletter-spacing: 0.08em;\n\t}\n\n\t.sb-empty-body\n\t{\n\t\tfont-size: $fs-2;\n\t\tfont-weight: 500;\n\t\tcolor: $text-faint;\n\t\tfont-family: $font-mono;\n\t\ttext-align: center;\n\t}\n\n\t.sb-tabs-arrow\n\t{\n\t\tdisplay: flex;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 36px;\n\t\theight: 40px;\n\t\tborder-radius: 8px;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line-strong;\n\t\tcolor: $text;\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\tz-index: 2;\n\n\t\t&:hover\n\t\t{\n\t\t\tborder-color: $accent;\n\t\t\tcolor: $accent;\n\t\t}\n\n\t\t&:active\n\t\t{\n\t\t\tsound-in: \"ui.button.press\";\n\t\t\ttransform: scale(0.96);\n\t\t}\n\n\t\t&.is-disabled\n\t\t{\n\t\t\topacity: 0.35;\n\t\t\tcursor: default;\n\t\t\tpointer-events: none;\n\t\t}\n\t}\n\n\t.sb-tabs-scroll\n\t{\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tmin-width: 0;\n\t\theight: 100%;\n\t\t// Clip the track; scrolling is done via margin-left on .sb-tabs-track\n\t\t// because Panel.ScrollSize often stays 0 for horizontal flex content.\n\t\toverflow: hidden;\n\t\tpointer-events: all;\n\t}\n\n\t.sb-tabs-track\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: nowrap;\n\t\talign-items: center;\n\t\tgap: 6px;\n\t\t// Must grow with chips so content width > viewport (enables overflow).\n\t\twidth: auto;\n\t\tflex-shrink: 0;\n\t\theight: 100%;\n\t\tpadding: 2px 2px 4px;\n\t\t// margin-left is driven from code for arrow / wheel scrolling.\n\t}\n\n\t.sb-tab-chip\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tgap: 6px;\n\t\theight: 36px;\n\t\tpadding: 0 14px;\n\t\tborder-radius: 6px;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line-strong;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\twhite-space: nowrap;\n\n\t\t&:hover\n\t\t{\n\t\t\tborder-color: $accent;\n\t\t}\n\n\t\t&.is-active\n\t\t{\n\t\t\tbackground-color: $accent;\n\t\t\tborder-color: $accent;\n\t\t\tcolor: $accent-ink;\n\n\t\t\t.sb-tab-chip-label\n\t\t\t{\n\t\t\t\tcolor: $accent-ink;\n\t\t\t}\n\t\t}\n\n\t\t&.is-search\n\t\t{\n\t\t\tbackground-color: #2a6df4;\n\t\t\tborder-color: #5b8dff;\n\t\t\tcolor: #fff;\n\n\t\t\t.sb-tab-chip-label\n\t\t\t{\n\t\t\t\tcolor: #fff;\n\t\t\t}\n\t\t}\n\n\t\t&:active\n\t\t{\n\t\t\tsound-in: \"ui.button.press\";\n\t\t\ttransform: scale(0.97);\n\t\t}\n\t}\n\n\t.sb-tab-chip-icon\n\t{\n\t\tfont-size: 13px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.sb-tab-chip-label\n\t{\n\t\tfont-size: $fs-2;\n\t\tfont-weight: 700;\n\t\tfont-family: $font-mono;\n\t\tletter-spacing: 1.2px;\n\t\tcolor: $text-dim;\n\t\twhite-space: nowrap;\n\t\tflex-shrink: 0;\n\t}\n\n\t.sb-board\n\t{\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tmin-height: 0;\n\t\tmargin-bottom: 14px;\n\t\tborder: 1px solid $line;\n\t\tbackground-color: $bg-1;\n\t\tflex-direction: column;\n\t\toverflow: hidden;\n\t\tpointer-events: all;\n\t}\n\n\t.sb-board-scroll\n\t{\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tmin-height: 0;\n\t\tflex-direction: column;\n\t\toverflow-y: scroll;\n\t\toverflow-x: hidden;\n\t\tpointer-events: all;\n\t}\n\n\t.sb-pad-content\n\t{\n\t\tflex-direction: column;\n\t\tflex-shrink: 0;\n\t\twidth: 100%;\n\t\tpointer-events: all;\n\t}\n\n\t.tile-grid\n\t{\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\talign-content: flex-start;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\tgap: 12px;\n\t\tpadding: 8px;\n\t\tpointer-events: all;\n\n\t\t.tile\n\t\t{\n\t\t\tpointer-events: all;\n\t\t\tflex-shrink: 0;\n\t\t}\n\t}\n\n\t.sb-pad-grid\n\t{\n\t\tpadding: 4px 4px 24px;\n\t}\n\n\t.tile\n\t{\n\t\twidth: 112px;\n\n\t\t&.is-loading\n\t\t{\n\t\t\tborder-color: $line-strong;\n\n\t\t\t.tile-swatch\n\t\t\t{\n\t\t\t\topacity: 0.55;\n\t\t\t}\n\n\t\t\t.tile-label\n\t\t\t{\n\t\t\t\tcolor: $text-dim;\n\t\t\t}\n\t\t}\n\n\t\t// Lobby co-op: pad lit by someone in the lobby (avatar + username).\n\t\t&.is-remote\n\t\t{\n\t\t\tborder-color: #5b8dff;\n\n\t\t\t.tile-label\n\t\t\t{\n\t\t\t\tcolor: #e8f0ff;\n\t\t\t\tbackground-color: rgba(42, 109, 244, 0.45);\n\t\t\t\tfont-weight: 800;\n\t\t\t}\n\t\t}\n\t}\n\n\t.tile-swatch\n\t{\n\t\theight: 64px;\n\t\tposition: relative;\n\t}\n\n\t.tile-remote-avatar\n\t{\n\t\tposition: absolute;\n\t\ttop: 4px;\n\t\tright: 4px;\n\t\twidth: 28px;\n\t\theight: 28px;\n\t\tborder-radius: 50%;\n\t\tbackground-size: cover;\n\t\tbackground-position: center;\n\t\tbackground-color: #0b1220;\n\t\tborder: 2px solid #ffffff;\n\t\tz-index: 3;\n\t\tpointer-events: none;\n\t}\n\n\t.tile-remote-name\n\t{\n\t\tposition: absolute;\n\t\tleft: 3px;\n\t\tright: 3px;\n\t\tbottom: 3px;\n\t\tpadding: 2px 4px;\n\t\tborder-radius: 4px;\n\t\tbackground-color: rgba(0, 0, 0, 0.72);\n\t\tcolor: #ffffff;\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tfont-family: $font-mono;\n\t\tletter-spacing: 0.04em;\n\t\ttext-align: center;\n\t\ttext-transform: none;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tz-index: 3;\n\t\tpointer-events: none;\n\t}\n\n\t.sb-controls\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tgap: 12px;\n\t\tflex-shrink: 0;\n\t\tmin-height: 56px;\n\t}\n\n\t// LOOP + CO-OP share one baseline.\n\t.sb-toggle-row\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tgap: 10px;\n\t}\n\n\t.sb-toggle\n\t{\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\twidth: 100px;\n\t\theight: 44px;\n\t\tmin-width: 100px;\n\t\tmin-height: 44px;\n\t\tmax-height: 44px;\n\t\tpadding: 0 12px;\n\t\tborder-radius: 10px;\n\t\tfont-size: 13px;\n\t\tfont-weight: 900;\n\t\tletter-spacing: 0.16em;\n\t\tcolor: #ffffff;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\t// s&box UI: use border shorthand only (border-style is invalid).\n\t\tborder: 3px solid #2a0000;\n\t\tbackground-color: #c10f0f;\n\t\tbox-shadow: 0 3px 0 #3a0000, inset 0 1px 4px rgba(255, 255, 255, 0.2);\n\t\ttext-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);\n\n\t\t&.is-on\n\t\t{\n\t\t\tborder: 3px solid #0a3d14;\n\t\t\tbackground-color: #1fbf3a;\n\t\t\tbox-shadow: 0 3px 0 #0f5a1c, 0 0 14px rgba(57, 255, 80, 0.35), inset 0 1px 4px rgba(255, 255, 255, 0.25);\n\t\t}\n\n\t\t&.is-off\n\t\t{\n\t\t\tborder: 3px solid #2a0000;\n\t\t\tbackground-color: #c10f0f;\n\t\t\tbox-shadow: 0 3px 0 #3a0000, inset 0 1px 4px rgba(255, 255, 255, 0.2);\n\t\t}\n\n\t\t// CO-OP on uses same green as LOOP (is-on); is-coop is optional marker.\n\t\t&.is-on.is-coop\n\t\t{\n\t\t\tborder: 3px solid #0a3d14;\n\t\t\tbackground-color: #1fbf3a;\n\t\t\tbox-shadow: 0 3px 0 #0f5a1c, 0 0 14px rgba(57, 255, 80, 0.35), inset 0 1px 4px rgba(255, 255, 255, 0.25);\n\t\t}\n\n\t\t&:active\n\t\t{\n\t\t\tsound-in: \"ui.button.press\";\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t}\n\n\t.sb-now-playing\n\t{\n\t\tflex-grow: 1;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 10px;\n\t\tmin-width: 0;\n\t\tpadding: 10px 14px;\n\t\tbackground-color: $bg-2;\n\t\tborder: 1px solid $line;\n\t}\n\n\t.sb-now-playing-dot\n\t{\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: $line-strong;\n\t\tflex-shrink: 0;\n\n\t\t&.is-live\n\t\t{\n\t\t\tbackground-color: $accent;\n\t\t}\n\t}\n\n\t.sb-now-playing-text\n\t{\n\t\tfont-size: $fs-2;\n\t\tfont-weight: 600;\n\t\tcolor: $text-dim;\n\t\tfont-family: $font-mono;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t}\n\n\t.sb-kill-btn\n\t{\n\t\tmin-width: 148px;\n\t\tflex-shrink: 0;\n\t}\n\n\t&.is-wide\n\t{\n\t\t.tile\n\t\t{\n\t\t\twidth: 120px;\n\t\t}\n\t}\n\n\t&.is-medium\n\t{\n\t\t.sb-shell\n\t\t{\n\t\t\tpadding: 18px 20px 16px;\n\t\t}\n\n\t\t.sb-title\n\t\t{\n\t\t\tfont-size: $fs-4;\n\t\t}\n\n\t\t.tile\n\t\t{\n\t\t\twidth: 104px;\n\t\t}\n\t}\n\n\t&.is-compact\n\t{\n\t\t.sb-shell\n\t\t{\n\t\t\tpadding: 14px 14px 12px;\n\t\t}\n\n\t\t.sb-voice-chip\n\t\t{\n\t\t\twidth: 116px;\n\t\t\tmin-width: 116px;\n\t\t\tmax-width: 116px;\n\t\t}\n\n\t\t.tile\n\t\t{\n\t\t\twidth: 96px;\n\t\t}\n\n\t\t.tile-swatch\n\t\t{\n\t\t\theight: 56px;\n\t\t}\n\t}\n\n\t&.is-tight\n\t{\n\t\t.sb-shell\n\t\t{\n\t\t\tpadding: 12px 10px 10px;\n\t\t}\n\n\t\t.sb-search-bar\n\t\t{\n\t\t\theight: 36px;\n\t\t\tpadding: 0 10px;\n\t\t}\n\n\t\t.sb-search-count\n\t\t{\n\t\t\tmin-width: 28px;\n\t\t\tfont-size: 10px;\n\t\t}\n\n\t\t.sb-topbar\n\t\t{\n\t\t\theight: 58px;\n\t\t\tmin-height: 58px;\n\t\t\tmax-height: 58px;\n\t\t}\n\n\t\t.sb-voice-chip\n\t\t{\n\t\t\twidth: 108px;\n\t\t\tmin-width: 108px;\n\t\t\tmax-width: 108px;\n\t\t\theight: 48px;\n\t\t\tmin-height: 48px;\n\t\t\tmax-height: 48px;\n\t\t}\n\n\t\t.sb-voice-chip-value\n\t\t{\n\t\t\tfont-size: $fs-3;\n\t\t}\n\n\t\t.sb-title\n\t\t{\n\t\t\tfont-size: $fs-3;\n\t\t}\n\n\t\t.sb-tabs-bar\n\t\t{\n\t\t\theight: 44px;\n\t\t}\n\n\t\t.sb-tab-chip\n\t\t{\n\t\t\theight: 32px;\n\t\t\tpadding: 0 10px;\n\t\t}\n\n\t\t.sb-tab-chip-label\n\t\t{\n\t\t\tfont-size: 11px;\n\t\t}\n\n\t\t.tile\n\t\t{\n\t\t\twidth: 88px;\n\t\t}\n\n\t\t.tile-swatch\n\t\t{\n\t\t\theight: 50px;\n\t\t}\n\n\t\t.sb-controls\n\t\t{\n\t\t\tflex-wrap: wrap;\n\t\t}\n\n\t\t.sb-now-playing\n\t\t{\n\t\t\twidth: 100%;\n\t\t\torder: 3;\n\t\t}\n\n\t\t.sb-kill-btn\n\t\t{\n\t\t\twidth: 100%;\n\t\t\torder: 2;\n\t\t}\n\t}\n\n\t&.has-voices\n\t{\n\t\t.sb-topbar\n\t\t{\n\t\t\tborder-bottom-color: $accent-line;\n\t\t}\n\t}\n\n\t.sb-ach\n\t{\n\t\tposition: absolute;\n\t\tleft: 24px;\n\t\tbottom: 28px;\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tgap: 14px;\n\t\tmax-width: 420px;\n\t\tpadding: 14px 18px;\n\t\tborder-radius: 12px;\n\t\tbackground-color: rgba(12, 8, 20, 0.94);\n\t\tborder: 2px solid rgba(255, 210, 80, 0.75);\n\t\tbox-shadow: 0 12px 36px rgba(0, 0, 0, 0.55), 0 0 24px rgba(255, 180, 40, 0.22);\n\t\tpointer-events: none;\n\t\tz-index: 50;\n\t}\n\n\t.sb-ach-icon\n\t{\n\t\tfont-size: 28px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.sb-ach-copy\n\t{\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 3px;\n\t\tmin-width: 0;\n\t}\n\n\t.sb-ach-kicker\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.2em;\n\t\tcolor: #ffd24a;\n\t}\n\n\t.sb-ach-title\n\t{\n\t\tfont-size: 15px;\n\t\tfont-weight: 900;\n\t\tletter-spacing: 0.04em;\n\t\tcolor: #fff;\n\t}\n\n\t.sb-ach-body\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 500;\n\t\tletter-spacing: 0.02em;\n\t\tcolor: rgba(255, 255, 255, 0.72);\n\t\twhite-space: normal;\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/Modal/ModalPanel.razor",
            "FileName": "ModalPanel.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@namespace Sunless.Libraries.UI\n@using Sandbox\n@using Sandbox.UI\n@attribute [StyleSheet]\n@inherits Panel\n\n<root class=\"modal-overlay @(IsOpen ? \"is-open\" : \"\") @ExtraClass\">\n\t@if ( IsOpen )\n\t{\n\t\t<div class=\"modal-scrim\" @onclick=@HandleScrimClick></div>\n\t\t<div class=\"modal-dialog\" style=@DialogStyle>\n\t\t\t@if ( Header is not null || !string.IsNullOrEmpty( Title ) || ShowClose )\n\t\t\t{\n\t\t\t\t<div class=\"modal-header\">\n\t\t\t\t\t@if ( Header is not null )\n\t\t\t\t\t{\n\t\t\t\t\t\t@Header\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t<div class=\"modal-title\">@Title</div>\n\t\t\t\t\t}\n\t\t\t\t\t@if ( ShowClose )\n\t\t\t\t\t{\n\t\t\t\t\t\t<div class=\"modal-close\" @onclick=@HandleClose>X</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t}\n\n\t\t\t@if ( Body is not null )\n\t\t\t{\n\t\t\t\t<div class=\"modal-body\">@Body</div>\n\t\t\t}\n\n\t\t\t@if ( Footer is not null )\n\t\t\t{\n\t\t\t\t<div class=\"modal-footer\">@Footer</div>\n\t\t\t}\n\t\t</div>\n\t}\n</root>\n\n@code\n{\n\tpublic bool IsOpen { get; set; }\n\tpublic string Title { get; set; }\n\tpublic bool ShowClose { get; set; } = true;\n\tpublic bool DismissOnScrim { get; set; } = true;\n\tpublic string ExtraClass { get; set; }\n\tpublic string DialogStyle { get; set; }\n\tpublic Action OnClose { get; set; }\n\n\tpublic RenderFragment Header { get; set; }\n\tpublic RenderFragment Body { get; set; }\n\tpublic RenderFragment Footer { get; set; }\n\n\tvoid HandleClose()\n\t{\n\t\ttry { OnClose?.Invoke(); } catch ( Exception e ) { Log.Warning( $\"[Modal] OnClose threw: {e.Message}\" ); }\n\t}\n\n\tvoid HandleScrimClick()\n\t{\n\t\tif ( !DismissOnScrim ) return;\n\t\tHandleClose();\n\t}\n\n\tprotected override int BuildHash() => HashCode.Combine( IsOpen, Title, ShowClose, ExtraClass, DialogStyle );\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/Toast/Toast.cs",
            "FileName": "Toast.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "namespace Sunless.Libraries.UI;\n\nusing System;\nusing Sandbox;\n\n// Singleton toast state. Host <ToastHost /> once. Call:\n//\n//   Toaster.Show( \"#toast.saved\" );\n//   Toaster.New().WithIcon( \"save\" ).WithCopy( path ).Show( \"#toast.saved\", path );\n//\npublic static class Toaster\n{\n\tpublic static string Title { get; private set; }\n\tpublic static string Message { get; private set; }\n\tpublic static string Icon { get; private set; }\n\tpublic static bool ShowCopy { get; private set; }\n\tpublic static string CopyValue { get; private set; }\n\tpublic static ToastTone Tone { get; private set; }\n\tpublic static bool IsOpen => !string.IsNullOrEmpty( Message );\n\tpublic static int Version { get; private set; }\n\n\tpublic static Action OnShowSound { get; set; }\n\tpublic static Action OnCopySound { get; set; }\n\n\tpublic static ToastBuilder New() => new();\n\n\tpublic static void Show( string message, string title = null )\n\t\t=> New().Show( message, title );\n\n\tpublic static void Dismiss()\n\t{\n\t\tif ( !IsOpen ) return;\n\t\tMessage = null;\n\t\tTitle = null;\n\t\tIcon = null;\n\t\tCopyValue = null;\n\t\tShowCopy = false;\n\t\tTone = ToastTone.Default;\n\t\tVersion++;\n\t}\n\n\tinternal static void RaiseCopySound()\n\t{\n\t\ttry { OnCopySound?.Invoke(); } catch ( Exception e ) { Log.Warning( $\"[Toaster] copy sound threw: {e.Message}\" ); }\n\t}\n\n\tinternal static void Commit( ToastBuilder b )\n\t{\n\t\tMessage = b._message;\n\t\tTitle = b._title;\n\t\tIcon = b._icon;\n\t\tShowCopy = b._showCopy;\n\t\tCopyValue = b._copyValue;\n\t\tTone = b._tone;\n\t\tVersion++;\n\t\ttry { (b._onShowSound ?? OnShowSound)?.Invoke(); } catch ( Exception e ) { Log.Warning( $\"[Toaster] show sound threw: {e.Message}\" ); }\n\t}\n}\n\npublic enum ToastTone { Default, Ok, Warn, Danger, Info }\n\npublic sealed class ToastBuilder\n{\n\tinternal string _title;\n\tinternal string _message;\n\tinternal string _icon;\n\tinternal bool _showCopy;\n\tinternal string _copyValue;\n\tinternal ToastTone _tone = ToastTone.Default;\n\tinternal Action _onShowSound;\n\n\tpublic ToastBuilder WithTitle( string title ) { _title = title; return this; }\n\tpublic ToastBuilder WithIcon( string ligature ) { _icon = ligature; return this; }\n\tpublic ToastBuilder WithCopy( string value = null ) { _showCopy = true; _copyValue = value; return this; }\n\tpublic ToastBuilder WithTone( ToastTone tone ) { _tone = tone; return this; }\n\tpublic ToastBuilder WithSound( Action sound ) { _onShowSound = sound; return this; }\n\n\tpublic void Show( string message, string title = null )\n\t{\n\t\t_message = message;\n\t\tif ( title is not null ) _title = title;\n\t\tToaster.Commit( this );\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "styles/base.scss",
            "FileName": "base.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"base/_splitcontainer.scss\";\r\n@import \"base/_navigator.scss\";\r\n\r\nbutton\r\n{\r\n\tcursor: pointer;\r\n}\r\n\r\nIconPanel\r\n{\r\n\tfont-family: Material Icons;\r\n}\r\n\r\n.is-half\r\n{\r\n\twidth: 50%;\r\n}\r\n\r\n.is-third\r\n{\r\n\twidth: 33%;\r\n}\r\n\r\n.is-quarter\r\n{\r\n\twidth: 25%;\r\n}\r\n\r\nbutton.has-subtitle\r\n{\r\n\tposition: relative;\r\n\tflex-direction: column;\r\n\tjustify-content: flex-start;\r\n\talign-items: flex-start;\r\n\tpadding-left: 40px; // icon space\r\n\r\n\t.iconpanel\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tleft: 5px;\r\n\t\ttop: 0;\r\n\t\tbottom: 0;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.button-label\r\n\t{\r\n\t\tfont-weight: bold;\r\n\t}\r\n\r\n\t.button-subtitle\r\n\t{\r\n\t\tfont-size: 12px;\r\n\t\topacity: 0.5;\r\n\t\tmix-blend-mode: lighten;\r\n\t}\r\n}"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/controlsheet/controlsheetrow.cs.scss",
            "FileName": "controlsheetrow.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "\r\nControlSheetRow\r\n{\r\n\tflex-direction: row;\r\n\tflex-shrink: 0;\r\n\tmax-height: 32px;\r\n\tborder-radius: 4px;\r\n\r\n\t&.hidden\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}\r\n\r\nControlSheetRow > .left\r\n{\r\n\twidth: 150px;\r\n\tflex-shrink: 0;\r\n\tflex-grow: 0;\r\n\tpadding: 6px;\r\n}\r\n\r\nControlSheetRow > .right\r\n{\r\n\tflex-grow: 1;\r\n}\r\n\r\nControlSheetRow > .left > .title\r\n{\r\n\twidth: 150px;\r\n\tflex-shrink: 0;\r\n\tflex-grow: 0;\r\n\tfont-size: 13px;\r\n\twhite-space: nowrap;\r\n\toverflow: hidden;\r\n}\r\n\r\nControlSheetRow > .right > textentry,\r\nControlSheetRow > .right numberentry\r\n{\r\n\tbackground-color: #000a;\r\n\tflex-grow: 1;\r\n\tpadding: 4px 8px;\r\n\tborder-radius: 2px;\r\n}\r\n\r\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/floatdock/floatdock.scss",
            "FileName": "floatdock.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n\n.float-dock {\n\tpointer-events: all;\n\tbackground-color: $bg-1;\n\tborder: 1px solid $line-strong;\n\tborder-left: 2px solid @import \"/ui/styles/_theme.scss\";\n\n.float-dock {\n\tpointer-events: all;\n\tbackground-color: $bg-1;\n\tborder: 1px solid $line-strong;\n\tborder-left: 2px solid $accent;\n\tpadding: 10px 14px;\n\tflex-direction: column;\n\tgap: 8px;\n\n\t&.float-dock--row { flex-direction: row; align-items: center; }\n\t&.float-dock--quiet { border-left-color: $line-mid; }\n\t&.float-dock--focus { border-left-color: $accent; }\n\t&.float-dock--danger { border-left-color: $danger; }\n}\n_TMP_PRIMARY;\n\tpadding: 10px 14px;\n\tflex-direction: column;\n\tgap: 8px;\n\n\t&.float-dock--row { flex-direction: row; align-items: center; }\n\t&.float-dock--quiet { border-left-color: $line-mid; }\n\t&.float-dock--focus { border-left-color: $accent; }\n\t&.float-dock--danger { border-left-color: $danger; }\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "ui/components/inputglyph/inputglyph.razor.scss",
            "FileName": "inputglyph.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@import \"/ui/styles/_theme.scss\";\n\n.input-glyph {\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: center;\n\n\timg {\n\t\twidth: 32px;\n\t\theight: 32px;\n\t}\n\n\t.input-glyph__fallback {\n\t\tpadding: 3px 8px;\n\t\tbackground-color: rgba(255, 255, 255, 0.06);\n\t\tborder: 1px solid $line-strong;\n\t\tcolor: $text;\n\t\tfont-family: $font-mono;\n\t\tfont-size: $fs-1;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\ttext-transform: uppercase;\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/EmptyState/EmptyState.razor",
            "FileName": "EmptyState.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@namespace Sunless.Libraries.UI\n@using Sandbox\n@using Sandbox.UI\n@attribute [StyleSheet]\n@inherits Panel\n\n<root>\n\t<div class=\"empty\">\n\t\t<div class=\"empty-icon\">@Icon</div>\n\t\t<div class=\"empty-title\">@Title</div>\n\t\t@if ( !string.IsNullOrEmpty( Sub ) )\n\t\t{\n\t\t\t<div class=\"empty-sub\">@Sub</div>\n\t\t}\n\t</div>\n</root>\n\n@code\n{\n\tpublic string Icon { get; set; } = \"-- --\";\n\tpublic string Title { get; set; } = \"\";\n\tpublic string Sub { get; set; } = \"\";\n\n\tprotected override int BuildHash() => HashCode.Combine( Icon, Title, Sub );\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/Icon/Icon.razor",
            "FileName": "Icon.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@namespace Sunless.Libraries.UI\n@using Sandbox\n@using Sandbox.UI\n@attribute [StyleSheet]\n@inherits Panel\n\n<root class=\"icon @SizeClass @ExtraClass\">@Name</root>\n\n@code\n{\n\tpublic string Name { get; set; } = \"\";\n\tpublic IconSize Size { get; set; } = IconSize.Medium;\n\tpublic string ExtraClass { get; set; }\n\n\tstring SizeClass => Size switch\n\t{\n\t\tIconSize.Small  => \"icon-sm\",\n\t\tIconSize.Large  => \"icon-lg\",\n\t\tIconSize.XLarge => \"icon-xl\",\n\t\t_               => \"icon-md\",\n\t};\n\n\tprotected override int BuildHash() => HashCode.Combine( Name, Size, ExtraClass );\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/Menu/MenuWidgetBuilders.cs",
            "FileName": "MenuWidgetBuilders.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "namespace Sunless.Libraries.UI;\n\nusing System;\nusing System.Collections.Generic;\n\npublic sealed class MenuLinkBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuLinkWidget _w;\n\n\tinternal MenuLinkBuilder( MenuScreenBuilder screen, string label, string icon )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuLinkWidget { Label = label, Icon = icon, Style = MenuLinkStyle.Row };\n\t}\n\n\tpublic MenuLinkBuilder WithStyle( MenuLinkStyle style )  { _w.Style = style; return this; }\n\tpublic MenuLinkBuilder WithTone( MenuTone tone )         { _w.Tone = tone; return this; }\n\tpublic MenuLinkBuilder WithHint( string hint )           { _w.Hint = hint; return this; }\n\tpublic MenuLinkBuilder WhenEnabled( Func<bool> enabled ) { _w.Enabled = enabled; return this; }\n\tpublic MenuLinkBuilder WithGoTo( string routeId )        { _w.RouteId = routeId; return this; }\n\tpublic MenuLinkBuilder WithAction( Action invoke )       { _w.Invoke = invoke; return this; }\n\tpublic MenuLinkBuilder Grows()                           { _w.Grow = true; return this; }\n\n\tpublic MenuScreenBuilder EndLink()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuToggleBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuToggleWidget _w;\n\n\tinternal MenuToggleBuilder( MenuScreenBuilder screen, string label )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuToggleWidget { Label = label };\n\t}\n\n\tpublic MenuToggleBuilder WithHint( string hint )        { _w.Hint = hint; return this; }\n\tpublic MenuToggleBuilder WithGetter( Func<bool> get )   { _w.Get = get; return this; }\n\tpublic MenuToggleBuilder WithSetter( Action<bool> set ) { _w.Set = set; return this; }\n\n\tpublic MenuToggleBuilder Bind( Func<bool> get, Action<bool> set )\n\t{\n\t\t_w.Get = get;\n\t\t_w.Set = set;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndToggle()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuRangeBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuRangeWidget _w;\n\n\tinternal MenuRangeBuilder( MenuScreenBuilder screen, string label )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuRangeWidget { Label = label, Min = 0f, Max = 1f, Step = 0.01f, Format = \"0.00\" };\n\t}\n\n\tpublic MenuRangeBuilder WithHint( string hint )         { _w.Hint = hint; return this; }\n\tpublic MenuRangeBuilder WithRange( float min, float max ) { _w.Min = min; _w.Max = max; return this; }\n\tpublic MenuRangeBuilder WithStep( float step )            { _w.Step = step; return this; }\n\tpublic MenuRangeBuilder WithFormat( string format )       { _w.Format = format; return this; }\n\n\tpublic MenuRangeBuilder Bind( Func<float> get, Action<float> set )\n\t{\n\t\t_w.Get = get;\n\t\t_w.Set = set;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndRange()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuSegmentedBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuSegmentedWidget _w;\n\n\tinternal MenuSegmentedBuilder( MenuScreenBuilder screen, string label, string[] choices )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuSegmentedWidget { Label = label, Choices = choices };\n\t}\n\n\tpublic MenuSegmentedBuilder WithHint( string hint ) { _w.Hint = hint; return this; }\n\n\tpublic MenuSegmentedBuilder Bind( Func<int> get, Action<int> set )\n\t{\n\t\t_w.Get = get;\n\t\t_w.Set = set;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndSegmented()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuRadioBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuRadioWidget _w;\n\treadonly List<MenuRadioWidget.Option> _options = new();\n\n\tinternal MenuRadioBuilder( MenuScreenBuilder screen, string label )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuRadioWidget { Label = label };\n\t}\n\n\tpublic MenuRadioBuilder WithHint( string hint ) { _w.Hint = hint; return this; }\n\n\tpublic MenuRadioBuilder WithOption( string label, string icon = null, string hint = null )\n\t{\n\t\t_options.Add( new MenuRadioWidget.Option { Label = label, Icon = icon, Hint = hint } );\n\t\treturn this;\n\t}\n\n\tpublic MenuRadioBuilder Bind( Func<int> get, Action<int> set )\n\t{\n\t\t_w.Get = get;\n\t\t_w.Set = set;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndRadio()\n\t{\n\t\t_w.Options = _options;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuInfoBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuInfoWidget _w;\n\n\tinternal MenuInfoBuilder( MenuScreenBuilder screen, string label )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuInfoWidget { Label = label };\n\t}\n\n\tpublic MenuInfoBuilder WithIcon( string icon )         { _w.Icon = icon; return this; }\n\tpublic MenuInfoBuilder WithGetter( Func<string> get )  { _w.Get = get; return this; }\n\tpublic MenuInfoBuilder WithValue( string value )       { _w.Get = () => value; return this; }\n\n\tpublic MenuScreenBuilder EndInfo()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuTextFieldBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuTextFieldWidget _w;\n\n\tinternal MenuTextFieldBuilder( MenuScreenBuilder screen, string label )\n\t{\n\t\t_screen = screen;\n\t\t_w = new MenuTextFieldWidget { Label = label };\n\t}\n\n\tpublic MenuTextFieldBuilder WithHint( string hint ) { _w.Hint = hint; return this; }\n\n\tpublic MenuTextFieldBuilder Bind( Func<string> get, Action<string> set )\n\t{\n\t\t_w.Get = get;\n\t\t_w.Set = set;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndTextField()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\n// === Composite widgets ===============================================\n\npublic sealed class MenuHeroCardBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuHeroCardWidget _w = new();\n\n\tinternal MenuHeroCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuHeroCardBuilder WithTitle( string title )             { _w.Title = title; return this; }\n\tpublic MenuHeroCardBuilder WithSubtitle( string subtitle )       { _w.Subtitle = subtitle; return this; }\n\tpublic MenuHeroCardBuilder WithDescription( string description ) { _w.Description = description; return this; }\n\tpublic MenuHeroCardBuilder WithImage( string url )               { _w.Image = url; return this; }\n\tpublic MenuHeroCardBuilder WithIcon( string icon )               { _w.Icon = icon; return this; }\n\n\tpublic MenuScreenBuilder EndHeroCard()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuMapCardBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuMapCardWidget _w = new();\n\n\tinternal MenuMapCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuMapCardBuilder WithTitle( string title )             { _w.Title = title; return this; }\n\tpublic MenuMapCardBuilder WithDescription( string description ) { _w.Description = description; return this; }\n\tpublic MenuMapCardBuilder WithImage( string url )               { _w.Image = url; return this; }\n\n\tpublic MenuMapCardBuilder WithChip( string label, string icon = null, MenuTone tone = MenuTone.Confirm )\n\t{\n\t\t_w.ChipLabel = label;\n\t\t_w.ChipIcon = icon;\n\t\t_w.ChipTone = tone;\n\t\treturn this;\n\t}\n\n\tpublic MenuMapCardBuilder WithAction( string label, string icon, Action invoke, string routeId = null )\n\t{\n\t\t_w.ActionLabel = label;\n\t\t_w.ActionIcon = icon;\n\t\t_w.ActionInvoke = invoke;\n\t\t_w.ActionRouteId = routeId;\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndMapCard()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuMapGridBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuMapGridWidget _w = new();\n\treadonly List<MenuMapGridWidget.Card> _cards = new();\n\n\tinternal MenuMapGridBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuMapGridBuilder WithActive( Func<int> get )  { _w.Active = get; return this; }\n\tpublic MenuMapGridBuilder OnPick( Action<int> set )    { _w.Pick = set; return this; }\n\n\tpublic MenuMapCardCardBuilder WithCard( string title )\n\t\t=> new( this, title );\n\n\tinternal void AddCard( MenuMapGridWidget.Card card ) => _cards.Add( card );\n\n\tbool _closed;\n\n\tpublic MenuScreenBuilder EndMapGrid()\n\t{\n\t\tif ( _closed ) return _screen;\n\t\t_closed = true;\n\t\t_w.Cards = _cards;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuMapCardCardBuilder\n{\n\treadonly MenuMapGridBuilder _grid;\n\treadonly MenuMapGridWidget.Card _c;\n\n\tinternal MenuMapCardCardBuilder( MenuMapGridBuilder grid, string title )\n\t{\n\t\t_grid = grid;\n\t\t_c = new MenuMapGridWidget.Card { Title = title };\n\t}\n\n\tpublic MenuMapCardCardBuilder WithDescription( string text ) { _c.Description = text; return this; }\n\tpublic MenuMapCardCardBuilder WithImage( string url )        { _c.Image = url; return this; }\n\tpublic MenuMapCardCardBuilder WithBadge( string label, MenuTone tone = MenuTone.Confirm )\n\t{\n\t\t_c.BadgeLabel = label;\n\t\t_c.BadgeTone = tone;\n\t\treturn this;\n\t}\n\n\tpublic MenuMapCardCardBuilder ComingSoon( bool v = true ) { _c.ComingSoon = v; return this; }\n\tpublic MenuMapCardCardBuilder Locked( bool v = true )     { _c.Locked = v; return this; }\n\n\tpublic MenuMapGridBuilder EndCard()\n\t{\n\t\t_grid.AddCard( _c );\n\t\treturn _grid;\n\t}\n}\n\npublic sealed class MenuTileGridBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuTileGridWidget _w = new();\n\treadonly List<MenuTileGridWidget.Item> _items = new();\n\n\tinternal MenuTileGridBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuTileGridBuilder WithTile( string label, string color = null, string image = null )\n\t{\n\t\t_items.Add( new MenuTileGridWidget.Item { Label = label, Color = color, Image = image } );\n\t\treturn this;\n\t}\n\n\tpublic MenuTileGridBuilder WithActive( Func<int> get ) { _w.Active = get; return this; }\n\tpublic MenuTileGridBuilder OnPick( Action<int> set )   { _w.Pick = set; return this; }\n\n\tpublic MenuScreenBuilder EndTileGrid()\n\t{\n\t\t_w.Items = _items;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuPlayerListBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuPlayerListWidget _w = new();\n\treadonly List<MenuPlayerListWidget.Player> _players = new();\n\n\tinternal MenuPlayerListBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuPlayerListBuilder WithPlayer( string name, bool ready = false, bool host = false )\n\t{\n\t\t_players.Add( new MenuPlayerListWidget.Player { Name = name, Ready = ready, Host = host } );\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndPlayerList()\n\t{\n\t\t_w.Players = _players;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuKeyBindingsBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuKeyBindingsWidget _w = new();\n\treadonly List<MenuKeyBindingsWidget.Binding> _bindings = new();\n\n\tinternal MenuKeyBindingsBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuKeyBindingsBuilder WithBinding( string action, params string[] keys )\n\t{\n\t\t_bindings.Add( new MenuKeyBindingsWidget.Binding { Action = action, Keys = keys } );\n\t\treturn this;\n\t}\n\n\tpublic MenuScreenBuilder EndKeyBindings()\n\t{\n\t\t_w.Bindings = _bindings;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuPromptCardBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuPromptCardWidget _w = new() { Verb = \"Press\" };\n\n\tinternal MenuPromptCardBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuPromptCardBuilder WithVerb( string verb )      { _w.Verb = verb; return this; }\n\tpublic MenuPromptCardBuilder WithPrompt( string prompt )  { _w.Prompt = prompt; return this; }\n\tpublic MenuPromptCardBuilder WithGlyph( string ligature ) { _w.Glyph = ligature; return this; }\n\tpublic MenuPromptCardBuilder WithCost( int cost )         { _w.Cost = cost; return this; }\n\tpublic MenuPromptCardBuilder WhenDisabled( Func<bool> d ) { _w.Disabled = d; return this; }\n\tpublic MenuPromptCardBuilder WithHoldProgress( Func<float> p ) { _w.Progress = p; return this; }\n\n\tpublic MenuScreenBuilder EndPromptCard()\n\t{\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuActionRowBuilder\n{\n\treadonly MenuScreenBuilder _screen;\n\treadonly MenuActionRowWidget _w = new();\n\treadonly List<MenuLinkWidget> _links = new();\n\n\tinternal MenuActionRowBuilder( MenuScreenBuilder screen ) { _screen = screen; }\n\n\tpublic MenuActionRowLinkBuilder WithLink( string label, string icon = null )\n\t\t=> new( this, label, icon );\n\n\tinternal void AddLink( MenuLinkWidget link ) => _links.Add( link );\n\n\tpublic MenuScreenBuilder EndActionRow()\n\t{\n\t\t_w.Links = _links;\n\t\t_screen.AddWidget( _w );\n\t\treturn _screen;\n\t}\n}\n\npublic sealed class MenuActionRowLinkBuilder\n{\n\treadonly MenuActionRowBuilder _row;\n\treadonly MenuLinkWidget _w;\n\n\tinternal MenuActionRowLinkBuilder( MenuActionRowBuilder row, string label, string icon )\n\t{\n\t\t_row = row;\n\t\t_w = new MenuLinkWidget { Label = label, Icon = icon, Style = MenuLinkStyle.Button };\n\t}\n\n\tpublic MenuActionRowLinkBuilder WithTone( MenuTone tone )         { _w.Tone = tone; return this; }\n\tpublic MenuActionRowLinkBuilder WithGoTo( string routeId )        { _w.RouteId = routeId; return this; }\n\tpublic MenuActionRowLinkBuilder WithAction( Action invoke )       { _w.Invoke = invoke; return this; }\n\tpublic MenuActionRowLinkBuilder WhenEnabled( Func<bool> enabled ) { _w.Enabled = enabled; return this; }\n\tpublic MenuActionRowLinkBuilder Grows()                           { _w.Grow = true; return this; }\n\n\tpublic MenuActionRowBuilder EndLink()\n\t{\n\t\t_row.AddLink( _w );\n\t\treturn _row;\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "UI/Components/Menu/PromptCard.razor",
            "FileName": "PromptCard.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "@namespace Sunless.Libraries.UI\n@using Sandbox\n@using Sandbox.UI\n@inherits Panel\n@attribute [StyleSheet]\n\n<root class=\"@(IsDisabled ? \"disabled\" : \"\")\">\n\t<div class=\"prompt-frame\">\n\t\t<div class=\"prompt-vignette\"></div>\n\n\t\t<div class=\"prompt-content\">\n\t\t\t<div class=\"prompt-glyph\">\n\t\t\t\t@if ( !string.IsNullOrEmpty( Glyph ) )\n\t\t\t\t{\n\t\t\t\t\t<div class=\"kbd\">@Glyph</div>\n\t\t\t\t}\n\t\t\t</div>\n\n\t\t\t<div class=\"prompt-text\">\n\t\t\t\t<div class=\"prompt-verb\">@EffectiveVerb</div>\n\t\t\t\t<div class=\"prompt-label\">@Prompt</div>\n\t\t\t</div>\n\n\t\t\t@if ( Cost > 0 )\n\t\t\t{\n\t\t\t\t<div class=\"prompt-cost\">\n\t\t\t\t\t<div class=\"prompt-cost-value\">@Cost</div>\n\t\t\t\t\t<div class=\"prompt-cost-unit\">PTS</div>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</div>\n\n\t\t<div class=\"prompt-rule\">\n\t\t\t<div class=\"prompt-rule-fill\" @ref=\"ruleFill\"></div>\n\t\t</div>\n\t</div>\n</root>\n\n@code\n{\n\tpublic string Verb { get; set; } = \"Press\";\n\tpublic string Prompt { get; set; } = \"\";\n\tpublic string Glyph { get; set; } = \"E\";\n\tpublic int Cost { get; set; }\n\tpublic bool IsDisabled { get; set; }\n\tpublic Func<float> HoldProgressFn { get; set; }\n\n\tPanel ruleFill { get; set; }\n\n\tstring EffectiveVerb => IsDisabled && Cost > 0 ? \"Not enough points\" : Verb;\n\n\tpublic override void Tick()\n\t{\n\t\tif ( ruleFill is null ) return;\n\n\t\tvar p = HoldProgressFn?.Invoke() ?? 0f;\n\t\tif ( p < 0f ) p = 0f;\n\t\telse if ( p > 1f ) p = 1f;\n\n\t\truleFill.Style.Opacity = p > 0f ? 1f : 0f;\n\t\truleFill.Style.Width = Length.Percent( p * 100f );\n\t}\n\n\tprotected override int BuildHash() => HashCode.Combine( Verb, Prompt, Glyph, Cost, IsDisabled );\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "SoundBoardNet.cs",
            "FileName": "SoundBoardNet.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "using System;\n\nnamespace SSoundboard;\n\n/// <summary>\n/// Network relay for co-op pad presses. Host owns one scene object; clients\n/// call Rpc.Host then host rebroadcasts so every machine hears the chaos.\n/// </summary>\npublic sealed class SoundBoardNet : Component\n{\n\tpublic static SoundBoardNet Instance { get; private set; }\n\n\tprotected override void OnEnabled()\n\t{\n\t\tInstance = this;\n\t}\n\n\tprotected override void OnDisabled()\n\t{\n\t\tif ( Instance == this )\n\t\t\tInstance = null;\n\t}\n\n\tprotected override void OnDestroy()\n\t{\n\t\tif ( Instance == this )\n\t\t\tInstance = null;\n\t}\n\n\t/// <summary>Host creates a networked singleton when a multiplayer session is live.</summary>\n\tpublic static void EnsureHostRelay()\n\t{\n\t\ttry\n\t\t{\n\t\t\tif ( !Networking.IsActive )\n\t\t\t\treturn;\n\n\t\t\tif ( Instance.IsValid() )\n\t\t\t\treturn;\n\n\t\t\tif ( !Networking.IsHost )\n\t\t\t\treturn;\n\n\t\t\tvar go = new GameObject( true, \"SoundBoardNet\" );\n\t\t\tgo.Components.Create<SoundBoardNet>();\n\t\t\tgo.NetworkSpawn();\n\t\t\tLog.Info( \"[S&Soundboard] CO-OP network relay spawned\" );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\"[S&Soundboard] CO-OP relay spawn failed: {e.Message}\" );\n\t\t}\n\t}\n\n\t/// <summary>Local player pressed a pad while CO-OP is on \u2014 share with the lobby.</summary>\n\tpublic static void SharePadPress( SoundPad pad )\n\t{\n\t\tif ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )\n\t\t\treturn;\n\n\t\tif ( !SoundBoardCoop.Enabled )\n\t\t\treturn;\n\n\t\ttry\n\t\t{\n\t\t\tif ( !Networking.IsActive )\n\t\t\t\treturn;\n\n\t\t\tEnsureHostRelay();\n\n\t\t\tvar net = Instance;\n\t\t\tif ( !net.IsValid() )\n\t\t\t\treturn;\n\n\t\t\t// Calling Rpc on a non-networked object throws (\"must be networked\").\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif ( net.GameObject is null || !net.GameObject.Network.Active )\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t\tcatch\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tnet.RequestPad(\n\t\t\t\tpad.SoundId,\n\t\t\t\tpad.Label ?? \"\",\n\t\t\t\tpad.Color ?? \"#ffffff\",\n\t\t\t\tpad.Volume <= 0f ? 1f : pad.Volume );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\t// Never let lobby sync break local button presses.\n\t\t\tLog.Warning( $\"[S&Soundboard] CO-OP share failed: {e.GetType().Name}: {e.Message}\" );\n\t\t}\n\t}\n\n\t[Rpc.Host]\n\tvoid RequestPad( string soundId, string label, string color, float volume )\n\t{\n\t\ttry\n\t\t{\n\t\t\tif ( string.IsNullOrWhiteSpace( soundId ) )\n\t\t\t\treturn;\n\n\t\t\tvar caller = Rpc.Caller;\n\t\t\tvar name = ResolveDisplayName( caller );\n\t\t\tvar steamId = ResolveSteamId( caller );\n\n\t\t\t// Host rebroadcasts to everyone (including the original presser).\n\t\t\tReceivePad( soundId, label ?? \"\", color ?? \"#ffffff\", volume, name, steamId );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\"[S&Soundboard] CO-OP RequestPad failed: {e.GetType().Name}: {e.Message}\" );\n\t\t}\n\t}\n\n\tstatic string ResolveDisplayName( Connection caller )\n\t{\n\t\tif ( caller is null )\n\t\t\treturn \"Player\";\n\n\t\ttry\n\t\t{\n\t\t\tif ( !string.IsNullOrWhiteSpace( caller.DisplayName ) )\n\t\t\t\treturn caller.DisplayName;\n\t\t}\n\t\tcatch { /* ignore */ }\n\n\t\ttry\n\t\t{\n\t\t\tif ( !string.IsNullOrWhiteSpace( caller.Name ) )\n\t\t\t\treturn caller.Name;\n\t\t}\n\t\tcatch { /* ignore */ }\n\n\t\treturn \"Player\";\n\t}\n\n\tstatic long ResolveSteamId( Connection caller )\n\t{\n\t\tif ( caller is null )\n\t\t\treturn 0;\n\n\t\t// Prefer string parse \u2014 SteamId / Friend.Id shapes vary; never throw on cast.\n\t\ttry\n\t\t{\n\t\t\tvar id = ParseSteamId( caller.FriendInfo.Id );\n\t\t\tif ( id != 0 )\n\t\t\t\treturn id;\n\t\t}\n\t\tcatch { /* ignore */ }\n\n\t\ttry\n\t\t{\n\t\t\tvar id = ParseSteamId( caller.SteamId );\n\t\t\tif ( id != 0 )\n\t\t\t\treturn id;\n\t\t}\n\t\tcatch { /* ignore */ }\n\n\t\ttry\n\t\t{\n\t\t\treturn ParseSteamId( caller.OwnerSteamId );\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tstatic long ParseSteamId( object value )\n\t{\n\t\tif ( value is null )\n\t\t\treturn 0;\n\n\t\ttry\n\t\t{\n\t\t\tswitch ( value )\n\t\t\t{\n\t\t\t\tcase long l:\n\t\t\t\t\treturn l;\n\t\t\t\tcase ulong ul:\n\t\t\t\t\treturn unchecked( (long)ul );\n\t\t\t\tcase int i:\n\t\t\t\t\treturn i;\n\t\t\t\tcase uint ui:\n\t\t\t\t\treturn ui;\n\t\t\t}\n\t\t}\n\t\tcatch { /* fall through */ }\n\n\t\ttry\n\t\t{\n\t\t\tvar s = value.ToString();\n\t\t\tif ( string.IsNullOrWhiteSpace( s ) )\n\t\t\t\treturn 0;\n\t\t\t// SteamId.ToString() is typically the numeric account id.\n\t\t\tif ( long.TryParse( s, out var parsed ) )\n\t\t\t\treturn parsed;\n\t\t}\n\t\tcatch { /* ignore */ }\n\n\t\treturn 0;\n\t}\n\n\t[Rpc.Broadcast( NetFlags.HostOnly | NetFlags.Reliable )]\n\tvoid ReceivePad( string soundId, string label, string color, float volume, string playerName, long steamId )\n\t{\n\t\tSoundBoardCoop.OnNetworkPad( soundId, label, color, volume, playerName, steamId );\n\t}\n}\n"
        },
        {
            "Ident": "dogecoin.ssoundboard",
            "Path": "SoundPreloadService.cs",
            "FileName": "SoundPreloadService.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 341579,
            "Code": "using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nnamespace SSoundboard;\n\n/// <summary>\n/// On-demand sound loading. Prefer TryGet (already compiled); LoadAsync only on click.\n/// </summary>\npublic static class SoundPreloadService\n{\n\tenum LoadState { Pending, Loading, Ready, Failed }\n\n\tstatic readonly Dictionary<string, LoadState> _states = new( StringComparer.OrdinalIgnoreCase );\n\tstatic readonly List<string> _priority = new();\n\tstatic readonly Dictionary<string, SoundPad> _pendingPlays = new( StringComparer.OrdinalIgnoreCase );\n\n\tstatic int _loaded;\n\tstatic int _registered;\n\tstatic int _activeLoads;\n\tstatic bool _initialized;\n\tstatic bool _startupComplete;\n\n\tconst int MaxConcurrentLoads = 2;\n\tconst int StartupWarmCap = 32;\n\n\tpublic static event Action Changed;\n\n\tpublic static int LoadedCount => _loaded;\n\tpublic static bool IsInitialized => _initialized;\n\tpublic static bool IsStartupComplete => _startupComplete;\n\t/// <summary>Bumps when pending-play set changes \u2014 cheap UI rebuild signal.</summary>\n\tpublic static int PendingRevision { get; private set; }\n\n\tpublic static void Initialize()\n\t{\n\t\tif ( _initialized )\n\t\t\treturn;\n\n\t\t_initialized = true;\n\n\t\tvar warmed = 0;\n\t\tforeach ( var soundId in SoundBoardData.StartupSoundIds )\n\t\t{\n\t\t\tif ( string.IsNullOrWhiteSpace( soundId ) )\n\t\t\t\tcontinue;\n\n\t\t\tif ( TryMarkReadyIfPresent( Normalize( soundId ) ) )\n\t\t\t\twarmed++;\n\n\t\t\tif ( warmed >= StartupWarmCap )\n\t\t\t\tbreak;\n\t\t}\n\n\t\t_startupComplete = true;\n\t\tChanged?.Invoke();\n\t}\n\n\tstatic void EnsureInitialized()\n\t{\n\t\tif ( !_initialized )\n\t\t\tInitialize();\n\t}\n\n\tpublic static bool IsReady( string soundId )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( soundId ) )\n\t\t\treturn false;\n\n\t\tEnsureInitialized();\n\t\tvar key = Normalize( soundId );\n\n\t\tif ( _states.TryGetValue( key, out var state ) )\n\t\t{\n\t\t\tif ( state == LoadState.Ready )\n\t\t\t\treturn true;\n\t\t\tif ( state is LoadState.Failed or LoadState.Loading )\n\t\t\t\treturn false;\n\t\t}\n\n\t\treturn TryMarkReadyIfPresent( key );\n\t}\n\n\tpublic static void BumpPriority( string soundId )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( soundId ) )\n\t\t\treturn;\n\n\t\tEnsureInitialized();\n\t\tvar key = Normalize( soundId );\n\n\t\tif ( IsReady( key ) )\n\t\t\treturn;\n\n\t\tif ( _states.TryGetValue( key, out var state ) )\n\t\t{\n\t\t\tif ( state is LoadState.Loading or LoadState.Ready )\n\t\t\t\treturn;\n\n\t\t\tif ( state == LoadState.Failed )\n\t\t\t\t_states[key] = LoadState.Pending;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_states[key] = LoadState.Pending;\n\t\t\t_registered++;\n\t\t}\n\n\t\t_priority.Remove( key );\n\t\t_priority.Insert( 0, key );\n\t\tChanged?.Invoke();\n\t}\n\n\tpublic static void QueuePlayWhenReady( SoundPad pad )\n\t{\n\t\tif ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )\n\t\t\treturn;\n\n\t\tvar key = Normalize( pad.SoundId );\n\t\t_pendingPlays[key] = pad;\n\t\tPendingRevision++;\n\t\tBumpPriority( key );\n\t}\n\n\tpublic static bool TryTakePendingPlay( string soundId, out SoundPad pad )\n\t{\n\t\tvar removed = _pendingPlays.Remove( Normalize( soundId ), out pad );\n\t\tif ( removed )\n\t\t\tPendingRevision++;\n\t\treturn removed;\n\t}\n\n\tpublic static bool IsPendingPlay( string soundId )\n\t{\n\t\treturn _pendingPlays.ContainsKey( Normalize( soundId ) );\n\t}\n\n\tpublic static void Tick()\n\t{\n\t\tEnsureInitialized();\n\t\tTryPumpLoads();\n\t}\n\n\tstatic void TryPumpLoads()\n\t{\n\t\twhile ( _activeLoads < MaxConcurrentLoads && TryDequeue( out var soundId ) )\n\t\t{\n\t\t\tif ( TryMarkReadyIfPresent( soundId ) )\n\t\t\t{\n\t\t\t\tSoundBoardManager.OnSoundReady( soundId );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t_activeLoads++;\n\t\t\t_states[soundId] = LoadState.Loading;\n\t\t\t_ = LoadOneAsync( soundId );\n\t\t}\n\t}\n\n\tstatic bool TryDequeue( out string soundId )\n\t{\n\t\twhile ( _priority.Count > 0 )\n\t\t{\n\t\t\tvar next = _priority[0];\n\t\t\t_priority.RemoveAt( 0 );\n\n\t\t\tif ( !_states.TryGetValue( next, out var st ) || st != LoadState.Pending )\n\t\t\t\tcontinue;\n\n\t\t\tsoundId = next;\n\t\t\treturn true;\n\t\t}\n\n\t\tsoundId = null;\n\t\treturn false;\n\t}\n\n\tstatic async Task LoadOneAsync( string soundId )\n\t{\n\t\tvar path = ToSoundPath( soundId );\n\n\t\ttry\n\t\t{\n\t\t\tif ( ResourceLibrary.TryGet<SoundEvent>( path, out _ ) )\n\t\t\t{\n\t\t\t\tMarkReady( soundId );\n\t\t\t\tSoundBoardManager.OnSoundReady( soundId );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar loaded = await ResourceLibrary.LoadAsync<SoundEvent>( path );\n\t\t\tif ( loaded is null )\n\t\t\t{\n\t\t\t\tMarkFailed( soundId );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry { Sound.Preload( path ); }\n\t\t\tcatch { /* playable without preload */ }\n\n\t\t\tMarkReady( soundId );\n\t\t\tSoundBoardManager.OnSoundReady( soundId );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\"[S&Soundboard] Sound load skipped: {path} ({e.GetType().Name})\" );\n\t\t\tMarkFailed( soundId );\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t_activeLoads = Math.Max( 0, _activeLoads - 1 );\n\t\t}\n\t}\n\n\tstatic bool TryMarkReadyIfPresent( string key )\n\t{\n\t\ttry\n\t\t{\n\t\t\tif ( !ResourceLibrary.TryGet<SoundEvent>( ToSoundPath( key ), out _ ) )\n\t\t\t\treturn false;\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tMarkReady( key );\n\t\treturn true;\n\t}\n\n\tstatic void MarkReady( string key )\n\t{\n\t\tvar already = _states.TryGetValue( key, out var prev ) && prev == LoadState.Ready;\n\t\t_states[key] = LoadState.Ready;\n\t\tif ( !already )\n\t\t\t_loaded++;\n\t\tChanged?.Invoke();\n\t}\n\n\tstatic void MarkFailed( string key )\n\t{\n\t\t_states[key] = LoadState.Failed;\n\t\tif ( _pendingPlays.Remove( key ) )\n\t\t\tPendingRevision++;\n\t\tChanged?.Invoke();\n\t}\n\n\tstatic string Normalize( string soundId )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( soundId ) )\n\t\t\treturn soundId;\n\n\t\tsoundId = soundId.Replace( '\\\\', '/' ).Trim();\n\t\treturn soundId.EndsWith( \".sound\", StringComparison.OrdinalIgnoreCase )\n\t\t\t? soundId[..^6]\n\t\t\t: soundId;\n\t}\n\n\tstatic string ToSoundPath( string soundId ) => $\"{Normalize( soundId )}.sound\";\n}\n"
        }
    ]
}