Ui/DesignSystem/Showcase/PlotterShowcase.razor

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));
    }
}