This component showcases various UI elements and design system components, including Plotter Buttons, Cards, and Surfaces, demonstrating different interaction states and visual styles.
@namespace CitySim.DesignSystem.Showcase
@using Sandbox
@using Sandbox.UI
@using CitySim.DesignSystem
@using CitySim.Cards
@using CitySim.Match
@inherits PanelComponent
<root class="plotter-showcase @(section == 4 ? "world-mode" : "") @(largeText ? "large-text" : "")">
<div class="showcase-rail">
<div class="brand-kicker">@(GameText.Get("ui.plotter_showcase.plotter_design_review"))</div>
<div class="brand-title">@(GameText.Get("ui.plotter_showcase.plotter"))</div>
<div class="brand-copy">@(GameText.Get("ui.plotter_showcase.a_working_catalogue_of_the_pieces_used_to_build_a_city"))</div>
<div class="nav-heading">@(GameText.Get("ui.plotter_showcase.explore"))</div>
<PlotterButton Label=@(GameText.Get("ui.plotter_showcase.01_foundations")) Variant=@(section == 0 ? "primary" : "quiet") Clicked=@(() => SetSection(0)) />
<PlotterButton Label=@(GameText.Get("ui.plotter_showcase.02_buttons_states")) Variant=@(section == 1 ? "primary" : "quiet") Clicked=@(() => SetSection(1)) />
<PlotterButton Label=@(GameText.Get("ui.plotter_showcase.03_fields_feedback")) Variant=@(section == 2 ? "primary" : "quiet") Clicked=@(() => SetSection(2)) />
<PlotterButton Label=@(GameText.Get("ui.plotter_showcase.04_cards_resources")) Variant=@(section == 3 ? "primary" : "quiet") Clicked=@(() => SetSection(3)) />
<PlotterButton Label=@(GameText.Get("ui.plotter_showcase.05_in_the_world")) Variant=@(section == 4 ? "primary" : "quiet") Clicked=@(() => SetSection(4)) />
<PlotterButton Label=@(largeText ? GameText.Get("ui.plotter_showcase.text_preview_150") : GameText.Get("ui.plotter_showcase.text_preview_100")) Variant="secondary" Clicked=@(() => { largeText = !largeText; }) />
<div class="rail-footer">@(GameText.Get("ui.plotter_showcase.design_system_sample_scene"))</div>
</div>
@if (section == 4)
{
<div class="world-caption"><div class="eyebrow">@(GameText.Get("ui.plotter_showcase.05_in_the_world"))</div><div class="world-title">@(GameText.Get("ui.plotter_showcase.a_closer_look"))</div><div class="world-copy">@(GameText.Get("ui.plotter_showcase.cardboard_ui_placed_in_the_scene_use_this_view_to_compare_the_same_visua"))</div><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.replay_world_feedback")) Variant="secondary" Clicked=@ReplayWorld /></div>
}
else
{
<div class="showcase-content">
@if (section == 0)
{
<div class="eyebrow">@(GameText.Get("ui.plotter_showcase.01_foundations"))</div><div class="page-title">@(GameText.Get("ui.plotter_showcase.built_like_a_board_game"))</div>
<div class="page-intro">@(GameText.Get("ui.plotter_showcase.warm_paper_printed_ink_and_visible_edges_make_every_control_feel_like_a"))</div>
<div class="sample-row">
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.the_materials")) Raised=@true><ChildContent>
<div class="body-copy">@(GameText.Get("ui.plotter_showcase.a_small_set_of_surfaces_carries_the_whole_interface_the_dark_outline_giv"))</div>
<div class="swatches"><div class="swatch ink">@(GameText.Get("ui.plotter_showcase.ink"))</div><div class="swatch board">@(GameText.Get("ui.plotter_showcase.board"))</div><div class="swatch paper">@(GameText.Get("ui.plotter_showcase.paper"))</div><div class="swatch teal">@(GameText.Get("ui.plotter_showcase.teal"))</div><div class="swatch red">@(GameText.Get("ui.plotter_showcase.red"))</div></div>
</ChildContent></PlotterSurface>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.type_hierarchy"))><ChildContent>
<div class="type-display">@(GameText.Get("ui.plotter_showcase.make_room_for_a_little_city"))</div><div class="type-label">@(GameText.Get("ui.plotter_showcase.printed_label_01"))</div><div class="body-copy">@(GameText.Get("ui.plotter_showcase.readable_copy_belongs_on_a_quiet_surface_labels_numbers_and_actions_each"))</div>
</ChildContent></PlotterSurface>
</div>
<div class="footnote">@(GameText.Get("ui.plotter_showcase.select_a_section_on_the_left_to_try_the_pieces"))</div>
}
else if (section == 1)
{
<div class="eyebrow">@(GameText.Get("ui.plotter_showcase.02_buttons_states"))</div><div class="page-title">@(GameText.Get("ui.plotter_showcase.made_to_be_pressed"))</div>
<div class="page-intro">@(GameText.Get("ui.plotter_showcase.try_each_action_hover_focus_and_press_states_are_part_of_the_component"))</div>
<div class="sample-row">
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.action_hierarchy")) Raised=@true><ChildContent>
<div class="sample-line"><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.primary_action")) Clicked=@(() => SetMessage(GameText.Get("ui.plotter_showcase.primary_action_pressed"))) /><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.secondary")) Variant="secondary" Clicked=@(() => SetMessage(GameText.Get("ui.plotter_showcase.secondary_action_pressed"))) /><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.quiet_action")) Variant="quiet" Clicked=@(() => SetMessage(GameText.Get("ui.plotter_showcase.quiet_action_pressed"))) /></div>
<div class="sample-line"><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.disabled")) Disabled=@true /><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.focused_example")) Variant="secondary" Focused=@true /></div>
<div class="status-line">@message</div>
</ChildContent></PlotterSurface>
</div>
}
else if (section == 2)
{
<div class="eyebrow">@(GameText.Get("ui.plotter_showcase.03_fields_feedback"))</div><div class="page-title">@(GameText.Get("ui.plotter_showcase.small_moments_clear_signals"))</div>
<div class="page-intro">@(GameText.Get("ui.plotter_showcase.adjust_a_value_and_compare_feedback_tiers_each_tier_responds_briefly_and"))</div>
<div class="sample-row">
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.a_recessed_field"))><ChildContent>
<PlotterField Label=@(GameText.Get("ui.plotter_showcase.starting_coins")) Help=@(GameText.Get("ui.plotter_showcase.a_local_sample_value"))><ChildContent><div class="field-controls"><PlotterButton Label="−" Variant="quiet" Clicked=@(() => ChangeCoins(-1)) /><div class="field-value">@coins</div><PlotterButton Label="+" Variant="quiet" Clicked=@(() => ChangeCoins(1)) /></div></ChildContent></PlotterField>
</ChildContent></PlotterSurface>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.feedback_tiers"))><ChildContent>
<div class="sample-line"><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.select")) Variant="quiet" Clicked=@(() => Trigger(GameText.Get("ui.plotter_showcase.selection"), 0)) /><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.reward")) Variant="secondary" Clicked=@(() => Trigger(GameText.Get("ui.plotter_showcase.reward_3"), 1)) /><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.milestone")) Clicked=@(() => Trigger(GameText.Get("ui.plotter_showcase.milestone_2"), 2)) /></div>
<div class="feedback-stage"><div class="feedback-token" style=@FeedbackStyle>★</div><div class="feedback-text">@feedback</div></div>
<div class="small-copy">@(GameText.Get("ui.plotter_showcase.feedback_is_brief_and_never_blocks_input"))</div>
</ChildContent></PlotterSurface>
</div>
}
else
{
<div class="eyebrow">@(GameText.Get("ui.plotter_showcase.04_cards_resources"))</div><div class="page-title">@(GameText.Get("ui.plotter_showcase.a_city_in_your_hands"))</div>
<div class="page-intro">@(GameText.Get("ui.plotter_showcase.market_and_hand_cards_use_the_real_catalogue_hover_or_focus_a_tag_or_int"))</div>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.market_choose_a_lot")) Raised=@true><ChildContent>
<div class="cards card-row">
@for (var i = 0; i < marketIds.Length; i++) {
var index = i; var card = CardLibrary.Get(marketIds[i]);
<PlotterCard Card=@card Cost=@MinimumBid(card) Selected=@(selectedMarket == i) Disabled=@(i == 3) Clicked=@(() => SelectMarket(index)) ReferenceTag=@referenceTag ReferenceChanged=@SetReferenceTag TargetsChanged=@SetTargets />
}
</div>
<div class="small-copy">@(GameText.Get("ui.plotter_showcase.locked_lot", CardLibrary.Get(marketIds[selectedMarket]).Name))</div>
</ChildContent></PlotterSurface>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.interaction_targets"))><ChildContent>
<div class="body-copy">@TargetMessage</div>
<div class="target-row">
@foreach (var id in targetIds) { var target = CardLibrary.Get(id); <div class="target-piece @(MatchesTarget(target) ? "match" : "")"><div class="target-icon">@target.Icon</div><div>@(GameText.Plain(target.Name))</div></div> }
</div>
<div class="small-copy">@(GameText.Get("ui.plotter_showcase.highlights_identify_matching_categories_only_range_ownership_and_effect"))</div>
</ChildContent></PlotterSurface>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.your_hand_select_and_discard"))><ChildContent>
<div class="cards card-row hand-samples">
@for (var i = 0; i < handIds.Length; i++) {
var index = i; var card = CardLibrary.Get(handIds[i]);
<PlotterCard Card=@card BuildOnly=@true BuildCost=@(i == 1 ? 2 : 0) Compact=@true HotKey=@((i + 1).ToString()) Selected=@(selectedHand == i && !discarded[i]) Disabled=@discarded[i] ShowDiscard=@(selectedHand == i && !discarded[i]) Clicked=@(() => SelectHand(index)) Discarded=@(() => DiscardHand(index)) ReferenceTag=@referenceTag ReferenceChanged=@SetReferenceTag TargetsChanged=@SetTargets />
}
</div>
<div class="small-copy">@(GameText.Get("ui.plotter_showcase.park_shows_an_alternate_2_coin_build_fee_scenario_the_default_build_fee"))</div>
<div class="sample-line"><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.restore_hand")) Variant="secondary" Clicked=@RestoreHand /><span class="small-copy">@cardMessage</span></div>
</ChildContent></PlotterSurface>
<PlotterSurface Title=@(GameText.Get("ui.plotter_showcase.resources"))><ChildContent><div class="resource"><div class="resource-icon" style=@FeedbackStyle>★</div><div><div class="resource-value">@points</div><div class="type-label">@(GameText.Get("ui.plotter_showcase.city_points"))</div></div></div><PlotterButton Label=@(GameText.Get("ui.plotter_showcase.collect_3")) Clicked=@Collect /></ChildContent></PlotterSurface>
}
</div>
}
</root>
@code {
int section, selectedMarket, selectedHand = 1, coins = 9, points = 24, tier;
readonly string[] marketIds = { "house", "cafe", "bank", "monument" };
readonly MatchSettings sampleSettings = new();
readonly string[] handIds = { "shop", "park", "factory", "hospital", "eco-council" };
readonly string[] targetIds = { "house", "shop", "park", "lake", "street", "factory", "cafe" };
readonly bool[] discarded = new bool[5];
Tag referenceTag;
IReadOnlyList<InteractionTarget>targets = Array.Empty<InteractionTarget>();
string cardMessage = GameText.Get("ui.plotter_showcase.select_a_hand_card_to_reveal_its_discard_action");
bool largeText;
string message = GameText.Get("ui.plotter_showcase.ready_to_explore"), feedback = GameText.Get("ui.plotter_showcase.choose_a_feedback_tier");
float feedbackAt = -100f;
int? MinimumBid(CardDef card) => sampleSettings.Rarities.First(r => r.Rarity == card.Rarity).MinimumBid;
string TargetMessage => referenceTag != Tag.None ? GameText.Get("ui.plotter_showcase.showing_tags", referenceTag) : targets.Count > 0 ? GameText.Get("ui.plotter_showcase.showing") + string.Join(", ", targets.Select(t => t.Label)) : GameText.Get("ui.plotter_showcase.hover_or_focus_a_card_tag_or_interaction_to_highlight_matching_pieces");
void SetReferenceTag(Tag value) { referenceTag = value; if (value != Tag.None) targets = Array.Empty<InteractionTarget>(); }
void SetTargets(IReadOnlyList<InteractionTarget>value) { targets = value ?? Array.Empty<InteractionTarget>(); if (targets.Count > 0) referenceTag = Tag.None; }
bool MatchesTarget(CardDef card) => referenceTag != Tag.None && card.Tags.HasFlag(referenceTag) || targets.Any(t => InteractionMatcher.Matches(t, new BoardCell(0, 0, card.Id, Guid.Empty, 0, 0), Guid.Empty));
/// <summary>Set the visible sample for editor review or automated screenshots.</summary>
public void SelectReviewSection(int value) { section = Math.Clamp(value, 0, 4); ClearTargets(); }
/// <summary>Show a tag match in the local target samples for repeatable screenshots.</summary>
public void ReviewTag(Tag tag) { section = 3; targets = Array.Empty<InteractionTarget>(); referenceTag = tag; }
/// <summary>Show the real interaction targets for a catalogue card in the local target samples.</summary>
public void ReviewInteractions(string cardId) { section = 3; referenceTag = Tag.None; targets = CardPresentation.Targets(CardLibrary.Get(cardId)); }
void ClearTargets() { referenceTag = Tag.None; targets = Array.Empty<InteractionTarget>(); }
void SetSection(int value) { SelectReviewSection(value); }
void SetMessage(string value) { message = value; }
void ChangeCoins(int delta) { coins = Math.Clamp(coins + delta, 0, 99); }
void Trigger(string value, int strength) { feedback = value; tier = strength; feedbackAt = Time.Now; }
void SelectMarket(int value) { ClearTargets(); selectedMarket = value; Trigger(GameText.Get("ui.plotter_showcase.selected") + CardLibrary.Get(marketIds[value]).Name, 0); }
void SelectHand(int value) { ClearTargets(); selectedHand = value; cardMessage = GameText.Get("ui.plotter_showcase.selected") + CardLibrary.Get(handIds[value]).Name; Trigger(cardMessage, 0); }
void DiscardHand(int value) { discarded[value] = true; cardMessage = GameText.Get("ui.plotter_showcase.discarded") + CardLibrary.Get(handIds[value]).Name; Trigger(cardMessage, 1); }
void RestoreHand() { Array.Fill(discarded, false); cardMessage = GameText.Get("ui.plotter_showcase.hand_restored"); }
void ReplayWorld() { Scene.GetAllComponents<CitySim.Ui.DesignSystem.World.WorldPiecesShowcase>().FirstOrDefault()?.Replay(); }
void Collect() { points += 3; Trigger(GameText.Get("ui.plotter_showcase.reward_3"), 1); }
string FeedbackStyle {
get {
float t = Math.Clamp((Time.Now - feedbackAt) / 0.32f, 0f, 1f);
float bump = (1f - t) * (1f - t) * MathF.Sin(t * MathF.PI) * (0.12f + tier * 0.08f);
return FormattableString.Invariant($"transform: scale({1f + bump:0.###});");
}
}
protected override int BuildHash() {
int animation = Time.Now - feedbackAt < 0.32f ? (int)(Time.Now * 60f) : 0;
return HashCode.Combine(HashCode.Combine(section, selectedMarket, selectedHand, coins, points, largeText, referenceTag, cardMessage), message, feedback, animation, string.Join(",", targets.Select(t => t.Label)), string.Join(",", discarded));
}
}