GPUI Box GitHub

Components / display

GeoMap builder

A controlled local map with choropleth legend, selectable feature readout, and point overlays. Click selects projected geometry; captured drag/touch pans and pinch/Ctrl-wheel zooms about the pointer. Arrow keys pan, +/- zoom, Home resets, F fits, / cycle selection. Escape cancels a drag or clears selection. No handler means a read-only map.

A builder is RenderOnce. Construct and mount it in one expression.

use gpui_kit::display::GeoMap;

Reviewed in geography geography-scale.

Construct
new(id: impl Into<SharedString>, label: impl Into<SharedString>) -> Self
Options

Chain onto the value.

state(state: GeoState) -> Self
viewport(viewport: GeoViewport) -> Self
selected(selected: Option<SharedString>) -> Self
animate(animate: bool) -> Self
status_text(text: impl Into<SharedString>) -> Self
on_event(handler: impl Fn(GeoEvent, &mut Window, &mut App) + 'static) -> Self
Example · geography
fn geography(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let state = crate::motion::keyed::slot::<FixtureCamera>(
        &"scene.geography.camera".into(),
        window.window_handle().window_id(),
        cx,
    );
    let viewport = state.borrow().viewport;
    let selected = state.borrow().selected.clone();
    let data = state
        .borrow_mut()
        .data
        .get_or_insert_with(|| fixture(GeoProjection::Equirectangular))
        .clone();
    let seam = state.borrow_mut().seam.get_or_insert_with(|| Rc::new(GeoData::from_geojson(
        r#"{"type":"Feature","id":"seam-island","properties":{},"geometry":{"type":"Polygon","coordinates":[[[170,-20],[-170,-20],[-170,30],[170,30],[170,-20]],[[175,-5],[-175,-5],[-175,10],[175,10],[175,-5]]]}}"#,
        GeoProjection::WebMercator, GeoWorldPolicy::Auto,
        GeoColorDomain { minimum:0.0, maximum:40.0,minimum_label:"0 samples".into(),maximum_label:"40 samples".into(),missing_label:"Unobserved".into() },
        |_,_| Ok(GeoProperties {label:"Synthetic seam island".into(),value:Some(23.0),formatted_value:"23 samples".into()}),
    ).expect("synthetic seam geometry"))).clone();
    let camera_target = state.clone();
    let value_target = state.clone();
    let geometry_target = state.clone();
    stack(&theme).w(px(900.0))
        .child(caption(&theme,"Original synthetic geography · local geometry, no tiles or real territories"))
        .child(caption(&theme,"Drag / touch to pan · pinch or Ctrl-wheel to zoom · F fits · arrows / +/- / Home · [ and ] select"))
        .child(div().row().gap_token(&theme,Space::Sm)
            .child(Button::new("scene.geography.camera-target").label("Retarget camera").on_click(move |window,_| {
                let mut target=camera_target.borrow_mut();
                target.viewport=if target.viewport.zoom>1.0 {GeoViewport::default()} else {GeoViewport {center:GeoProjected{x:0.4,y:0.48},zoom:1.8}};
                window.refresh();
            }))
            .child(Button::new("scene.geography.value-target").label("Retarget observations").on_click(move |window,_| {
                let mut target=value_target.borrow_mut();
                let source=target.data.as_ref().expect("prepared fixture");
                let mut features=source.features().to_vec();
                let value=if features[0].value==Some(12.0) {34.0} else {12.0};
                features[0].value=Some(value); features[0].formatted_value=format!("{value} samples").into();
                target.data=Some(Rc::new(GeoData::new(source.projection(),features,source.points().to_vec(),source.color_domain().clone()).expect("in-domain fixture observation")));
                window.refresh();
            }))
            .child(Button::new("scene.geography.geometry-target").label("Replace geometry").on_click(move |window,_| {
                let mut target=geometry_target.borrow_mut();
                let source=target.data.as_ref().expect("prepared fixture");
                if source.features().iter().any(|f| f.id.as_ref()=="unobserved") {
                    let mut features=source.features().to_vec();features.retain(|f| f.id.as_ref()!="unobserved");
                    for p in features[0].polygons.iter_mut().flat_map(|p| std::iter::once(&mut p.exterior).chain(&mut p.holes)).flatten() {p.longitude+=40.0;}
                    let mut points=source.points().to_vec(); points.push(GeoPoint{id:"arriving".into(),label:"New overlapping sensor".into(),position:GeoPosition{longitude:-55.0,latitude:12.0}});
                    target.data=Some(Rc::new(GeoData::new(source.projection(),features,points,source.color_domain().clone()).expect("translated valid fixture")));
                } else {target.data=Some(fixture(GeoProjection::Equirectangular));}
                window.refresh();
            })))
        .child(div().row().items_start().gap_token(&theme,Space::Md)
            .child(div().w(px(500.0)).child(GeoMap::new("scene.geography.ready","Equirectangular · controlled camera")
                .state(GeoState::Ready(data)).viewport(viewport).selected(selected)
                .on_event(move |event,window,_| {
                    match event { GeoEvent::Select(id) => state.borrow_mut().selected=id, GeoEvent::Viewport(viewport) => state.borrow_mut().viewport=viewport }
                    window.refresh();
                })))
            .child(div().w(px(320.0)).child(GeoMap::new("scene.geography.zoomed","Web Mercator · narrow, zoomed, selected")
                .state(GeoState::Stale { data:fixture(GeoProjection::WebMercator), reason:"Refresh refused; verified geometry retained".into() })
                .viewport(GeoViewport { center:GeoProjected{x:0.38,y:0.46},zoom:2.2 }).selected(Some("lagoon".into())))))
        .child(div().row().items_start().gap_token(&theme,Space::Md)
            .child(div().w(px(280.0)).child(GeoMap::new("scene.geography.loading","Loading local features")))
            .child(div().w(px(280.0)).child(GeoMap::new("scene.geography.empty","Valid empty collection").state(GeoState::Empty)))
            .child(div().w(px(280.0)).child(GeoMap::new("scene.geography.refused","Unsupported input").state(GeoState::Refused(GeoRefusal::AntimeridianEdge)))))
        .child(GeoMap::new("scene.geography.seam","Local GeoJSON · auto world cut · fitted island with a real hole")
            .viewport(seam.fit_viewport([850.0,280.0],16.0).expect("fit synthetic island"))
            .selected(Some("seam-island".into())).state(GeoState::Ready(seam)))
        .into_any_element()
}

pub(super) fn geography_scale(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let state = crate::motion::keyed::slot::<FixtureCamera>(
        &"scene.geography.scale-state".into(),
        window.window_handle().window_id(),
        cx,
    );
    if state.borrow().data.is_none() {
        state.borrow_mut().viewport = GeoViewport {
            center: GeoProjected { x: 0.5, y: 0.5 },
            zoom: 8.0,
        };
    }
    let data = state
        .borrow_mut()
        .data
        .get_or_insert_with(|| {
            Rc::new(
                GeoData::new(
                    GeoProjection::Equirectangular,
                    vec![],
                    (0..1000)
                        .map(|i| GeoPoint {
                            id: format!("sample-{i}").into(),
                            label: format!("Synthetic sample {i}").into(),
                            position: GeoPosition {
                                longitude: -150.0 + f64::from(i % 100) * 3.0,
                                latitude: -45.0 + f64::from(i / 100) * 10.0,
                            },
                        })
                        .collect(),
                    GeoColorDomain {
                        minimum: 0.0,
                        maximum: 1.0,
                        minimum_label: "0".into(),
                        maximum_label: "1".into(),
                        missing_label: "Unobserved".into(),
                    },
                )
                .expect("original point grid"),
            )
        })
        .clone();
    let selected = state.borrow().selected.clone();
    let viewport = state.borrow().viewport;
    let dense = state.clone();
    stack(&theme).w(px(900.0))
        .child(caption(&theme,format!("{} original local point overlays · viewport culling · six-row virtual accessible readout",data.points().len())))
        .child(caption(&theme,"Scroll the readout or navigate it by keyboard; offscreen rows retain source identities, not measured bounds"))
        .child(Button::new("scene.geography.scale-density").label("Load 10,000 dense points").on_click(move |window,_| {
            let mut target=dense.borrow_mut();
            let points=(0..10000).map(|i| GeoPoint{id:format!("sample-{i}").into(),label:format!("Synthetic sample {i}").into(),position:GeoPosition{longitude:-179.0+f64::from(i%1000)*0.358,latitude:-80.0+f64::from(i/1000)*1.6}}).collect();
            let data=GeoData::new(GeoProjection::Equirectangular,vec![],points,target.data.as_ref().expect("prepared fixture").color_domain().clone()).expect("original point grid");
            target.viewport=GeoViewport{center:data.projection().project(data.points()[5000].position).expect("prepared fixture"),zoom:1.0};
            target.data=Some(Rc::new(data));
            window.refresh();
        }))
        .child(GeoMap::new("scene.geography.scale","Controlled point cloud").state(GeoState::Ready(data)).viewport(viewport).selected(selected)
            .on_event(move |event,window,_| {match event {GeoEvent::Select(id)=>state.borrow_mut().selected=id,GeoEvent::Viewport(v)=>state.borrow_mut().viewport=v};window.refresh();}))
        .into_any_element()
}

//! Explicit continuous color encodings, kept separate from contribution levels.
use super::support::*;
use crate::display::heatmap::{ContinuousHeatCell, ContinuousHeatmap, HeatColorScale};

#[derive(Default)]
struct MatrixRevisions {
    revision: usize,
    direct: bool,
    selected: Option<SharedString>,
}

pub(super) fn heatmap_reordering(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let state = crate::motion::keyed::slot::<MatrixRevisions>(
        &"scene.heat.reorder-state".into(),
        window.window_handle().window_id(),
        cx,
    );
    let revision = state.borrow().revision % 3;
    let rows = match revision {
        0 => vec!["east", "west"],
        1 => vec!["west", "north", "east"],
        _ => vec!["east"],
    };
    let columns = match revision {
        0 => vec!["a", "b", "c"],
        1 => vec!["c", "a", "d", "b"],
        _ => vec!["a", "b"],
    };
    let cells = rows
        .iter()
        .flat_map(|row| {
            columns.iter().map(move |column| {
                let reading = match (*row, *column) {
                    ("east", "a") => Some(3.0),
                    ("east", "b") => Some(12.0),
                    ("west", "a") => Some(42.0),
                    ("west", "c") => Some(81.0),
                    ("north", "b") => Some(65.0),
                    _ => None,
                };
                ContinuousHeatCell::new(
                    format!("{row}-{column}"),
                    *row,
                    *column,
                    format!("{row} / {column}"),
                    reading,
                )
            })
        })
        .collect();
    let next = state.clone();
    let direct = state.clone();
    let selection = state.clone();
    stack(&theme)
        .w(px(560.0))
        .child(caption(
            &theme,
            "Synthetic keyed axes · reorder, enter, exit and reinsert · raw readings never tween",
        ))
        .child(
            div()
                .row()
                .gap_token(&theme, Space::Sm)
                .child(
                    Button::new("scene.heat.reorder.advance")
                        .label("Advance axes")
                        .on_click(move |window, _| {
                            next.borrow_mut().revision += 1;
                            window.refresh();
                        }),
                )
                .child(
                    Button::new("scene.heat.reorder.direct")
                        .label(if state.borrow().direct {
                            "Enable animation"
                        } else {
                            "Direct updates"
                        })
                        .on_click(move |window, _| {
                            let mut s = direct.borrow_mut();
                            s.direct = !s.direct;
                            window.refresh();
                        }),
                ),
        )
        .child(
            ContinuousHeatmap::new(
                "scene.heat.reorder",
                "Stable caller coordinates",
                HeatColorScale::sequential([0.0, 100.0], theme.colors.canvas, theme.colors.accent)
                    .expect("domain"),
                PlotState::Ready(cells),
            )
            .rows(rows)
            .columns(columns)
            .animate(!state.borrow().direct)
            .on_current(move |id, window, _| {
                selection.borrow_mut().selected = Some(id);
                window.refresh();
            })
            .when_some(state.borrow().selected.clone(), |chart, id| {
                chart.current(id)
            }),
        )
        .into_any_element()
}

pub(super) fn continuous_heatmap_transition(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let revision = crate::motion::keyed::slot::<usize>(
        &"scene.heat.transition-state".into(),
        window.window_handle().window_id(),
        cx,
    );
    let value = match *revision.borrow() % 3 {
        0 => Some(-8.0),
        1 => Some(32.0),
        _ => None,
    };
    let next = revision.clone();
    stack(&theme)
        .w(px(440.0))
        .child(caption(
            &theme,
            "Synthetic observations · exact latest text with animated color; missing is never zero",
        ))
        .child(
            Button::new("scene.heat.advance")
                .label("Advance reading")
                .on_click(move |window, _| {
                    *next.borrow_mut() += 1;
                    window.refresh();
                }),
        )
        .child(
            ContinuousHeatmap::new(
                "scene.heat.live",
                "Controlled measurement",
                HeatColorScale::diverging(
                    [-8.0, 2.0, 32.0],
                    theme.colors.accent,
                    theme.colors.canvas,
                    theme.colors.danger,
                )
                .expect("domain"),
                PlotState::Ready(vec![
                    ContinuousHeatCell::new("changing", "row", "a", "Changing reading", value),
                    ContinuousHeatCell::new("zero", "row", "b", "Verified zero", Some(0.0)),
                ]),
            )
            .rows(["row"])
            .columns(["a", "b"])
            .motion(true),
        )
        .into_any_element()
}

pub(super) fn continuous_heatmap(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let diverging = HeatColorScale::diverging(
        [-8.0, 2.0, 32.0],
        theme.colors.accent,
        theme.colors.canvas,
        theme.colors.danger,
    )
    .expect("explicit domain");
    let sequential =
        HeatColorScale::sequential([0.0, 100.0], theme.colors.canvas, theme.colors.success)
            .expect("explicit domain");
    let cells = [
        ("east", "a", Some(-8.0)),
        ("east", "b", Some(0.0)),
        ("east", "c", Some(2.0)),
        ("west", "a", Some(17.0)),
        ("west", "b", Some(32.0)),
        ("west", "c", None),
    ]
    .into_iter()
    .map(|(row, column, reading)| {
        ContinuousHeatCell::new(
            format!("{row}-{column}"),
            row,
            column,
            format!("Synthetic observation {row} / {column}"),
            reading,
        )
    })
    .collect::<Vec<_>>();
    stack(&theme)
        .w(px(800.0))
        .child(caption(
            &theme,
            "Synthetic measurements · explicit neutral = 2 · no observation is not zero",
        ))
        .child(
            div().w(px(720.0)).child(
                ContinuousHeatmap::new(
                    "scene.continuous.wide",
                    "Diverging measurements",
                    diverging,
                    PlotState::Ready(cells.clone()),
                )
                .rows(["east", "west"])
                .columns(["a", "b", "c"])
                .current("east-c")
                .on_current(|_, _, _| {}),
            ),
        )
        .child(
            div().w(px(240.0)).child(
                ContinuousHeatmap::new(
                    "scene.continuous.narrow",
                    "Narrow · last verified values",
                    diverging,
                    PlotState::Stale {
                        data: cells,
                        reason: "Refresh refused".into(),
                    },
                )
                .rows(["east", "west"])
                .columns(["a", "b", "c"]),
            ),
        )
        .child(
            div().w(px(400.0)).child(
                ContinuousHeatmap::new(
                    "scene.continuous.sequential",
                    "Sequential · domain 0–100",
                    sequential,
                    PlotState::Ready(vec![
                        ContinuousHeatCell::new("zero", "r", "a", "Measured zero", Some(0.0)),
                        ContinuousHeatCell::new("middle", "r", "b", "Middle", Some(50.0)),
                        ContinuousHeatCell::new("high", "r", "c", "High", Some(100.0)),
                    ]),
                )
                .rows(["r"])
                .columns(["a", "b", "c"]),
            ),
        )
        .child(
            ContinuousHeatmap::new(
                "scene.continuous.invalid",
                "Invalid readings are errors",
                sequential,
                PlotState::Ready(vec![ContinuousHeatCell::new(
                    "outside",
                    "r",
                    "c",
                    "Outside domain",
                    Some(101.0),
                )]),
            )
            .rows(["r"])
            .columns(["c"]),
        )
        .into_any_element()
}

//! Mobile list interactions, with caller-owned async state and retained rows.

use super::support::*;
use crate::interaction::{
    refresh::{PullToRefresh, RefreshState},
    swipe::{SwipeActions, SwipeSide},
};
use crate::overlay::sheet::{SheetAction, SheetActionState};

struct SceneRefresh(Vec<Entity<PullToRefresh>>);
impl Global for SceneRefresh {}

pub(super) fn pull_to_refresh(_window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneRefresh>() {
        let states = [
            ("ready", RefreshState::Ready),
            (
                "pending",
                RefreshState::Pending("Waiting for caller".into()),
            ),
            (
                "loading",
                RefreshState::Loading("Refreshing fixture".into()),
            ),
            (
                "error",
                RefreshState::Error("Refresh failed · retained rows".into()),
            ),
        ];
        let mut views = Vec::new();
        for (id, state) in states {
            let scroll = gpui::ScrollHandle::new();
            let view = cx.new(|_| {
                PullToRefresh::new(
                    format!("scene.refresh.{id}"),
                    scroll.clone(),
                    "Refresh",
                    "Release to refresh",
                )
            });
            view.update(cx, |refresh, cx| {
                refresh.set_state(state, cx);
                refresh.set_content(
                    Some(Rc::new(move |_, cx| {
                        let theme = cx.theme().clone();
                        div()
                            .id(SharedString::from(format!("scene.refresh.{id}.scroll")))
                            .column()
                            .size_full()
                            .overflow_y_scroll()
                            .track_scroll(&scroll)
                            .children(
                                [
                                    ("notes", "Notes · verified fixture"),
                                    ("draft", "Draft · verified fixture"),
                                    ("archive", "Archive · verified fixture"),
                                    ("saved", "Saved · verified fixture"),
                                ]
                                .into_iter()
                                .map(|(key, label)| {
                                    div()
                                        .h(px(64.0))
                                        .flex_none()
                                        .p_token(&theme, Space::Sm)
                                        .child(crate::foundation::text(
                                            &theme,
                                            TypeScale::Body,
                                            label,
                                        ))
                                        .semantic_in(
                                            cx,
                                            NodeSpec::new(
                                                format!("scene.refresh.{id}.{key}"),
                                                Role::Group,
                                            )
                                            .text(label),
                                        )
                                }),
                            )
                            .into_any_element()
                    })),
                    cx,
                );
            });
            views.push(view);
        }
        cx.set_global(SceneRefresh(views));
    }
    let theme = cx.theme().clone();
    stack(&theme)
        .w_full()
        .child(caption(
            &theme,
            "Fixture states · refreshing never replaces the verified rows",
        ))
        .child(
            row(&theme).items_start().children(
                cx.global::<SceneRefresh>()
                    .0
                    .iter()
                    .map(|view| div().w(px(250.0)).h(px(370.0)).child(view.clone())),
            ),
        )
        .into_any_element()
}

struct SceneSwipe(Vec<Entity<SwipeActions>>);
impl Global for SceneSwipe {}

pub(super) fn swipe_actions(_window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneSwipe>() {
        let mut views = Vec::new();
        for (id, side, state) in [
            ("closed", None, SheetActionState::Ready),
            ("left", Some(SwipeSide::Left), SheetActionState::Ready),
            (
                "right",
                Some(SwipeSide::Right),
                SheetActionState::Error("Deletion failed; row retained".into()),
            ),
            (
                "pending",
                Some(SwipeSide::Right),
                SheetActionState::Pending("Waiting for caller".into()),
            ),
        ] {
            let view = cx.new(|_| SwipeActions::new(format!("scene.swipe.{id}"), "Actions"));
            view.update(cx, |swipe, cx| {
                let mut remove = SheetAction::new("remove", "Delete");
                remove.destructive = true;
                swipe.set_actions(SwipeSide::Left, vec![SheetAction::new("pin", "Pin")], cx);
                swipe.set_actions(SwipeSide::Right, vec![remove], cx);
                swipe.set_content(
                    Some(Rc::new(move |_, cx| {
                        let theme = cx.theme().clone();
                        div()
                            .h(px(64.0))
                            .column()
                            .justify_center()
                            .child(crate::foundation::text(
                                &theme,
                                TypeScale::Label,
                                "Project notes · fixture",
                            ))
                            .child(crate::foundation::text(
                                &theme,
                                TypeScale::Body,
                                "Last verified content remains visible",
                            ))
                            .semantic_in(
                                cx,
                                NodeSpec::new(format!("scene.swipe.{id}.content"), Role::Group)
                                    .text("Project notes · fixture"),
                            )
                            .into_any_element()
                    })),
                    cx,
                );
                swipe.reveal(side, cx);
                swipe.set_state(state, cx);
            });
            views.push(view);
        }
        cx.set_global(SceneSwipe(views));
    }
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(580.0))
        .child(caption(
            &theme,
            "Reveal is not activation · physical left and right trays",
        ))
        .children(cx.global::<SceneSwipe>().0.iter().cloned())
        .into_any_element()
}

//! Frames that decide where their contents go.

use super::support::*;

pub(super) fn mobile_page(_window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::layout::{AppBar, PageLayout};
    let theme = cx.theme().clone();
    let page = |name: &'static str, keyboard: bool, safe: bool| {
        let id = format!("scene.mobile-page.{name}");
        let mut insets = gpui::WindowInsets::default();
        if safe {
            insets.safe_area.top = px(24.0);
            insets.safe_area.bottom = px(20.0);
            insets.safe_area.left = px(7.0);
            insets.safe_area.right = px(11.0);
        }
        if keyboard {
            insets.ime.bottom = px(140.0);
        }
        div()
            .w(px(290.0))
            .h(px(430.0))
            .border_1()
            .border_color(theme.colors.control_hairline)
            .surface(&theme, Surface::Panel)
            .child(
                PageLayout::new(
                    id.clone(),
                    ScrollArea::new(format!("{id}.scroll")).child(filler(
                        &theme,
                        "Fixture page content",
                        12,
                    )),
                )
                .insets(insets)
                .hide_footer(keyboard)
                .header(
                    AppBar::new(format!("{id}.bar"), name).leading(
                        Button::new(format!("{id}.back"))
                            .label("Back")
                            .ghost()
                            .control_size(ControlSize::Touch)
                            .disabled(!safe)
                            .on_click(|_, _| {}),
                    ),
                )
                .footer(div().p(px(theme.space(Space::Sm))).child("Caller footer")),
            )
    };
    stack(&theme)
        .child(caption(&theme, "Fixture residual insets: desktop zero · safe area · overlapping IME with footer unmounted"))
        .child(row(&theme).items_start().child(page("Desktop", false, false))
            .child(page("Safe area", false, true)).child(page("Keyboard", true, true)))
        .into_any_element()
}

pub(super) fn scroll_edge_effect(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(620.0))
        .child(caption(
            &theme,
            "Fixture transcript: content scrolls beneath a Regular glass toolbar",
        ))
        .child(
            div()
                .relative()
                .h(px(240.0))
                .overflow_hidden()
                .child(
                    crate::layout::ScrollEdgeEffect::new("scene.scroll-edge.soft")
                        .top(true)
                        .soft()
                        .band(64.0)
                        .child(filler(&theme, "Earlier messages beneath the toolbar", 10)),
                )
                .child(
                    div().absolute().top(px(8.0)).left(px(16.0)).child(
                        Glass::new("scene.scroll-edge.toolbar")
                            .radius(Radius::Pill)
                            .child(
                                div()
                                    .px(px(20.0))
                                    .py(px(10.0))
                                    .child("Transcript · fixture"),
                            ),
                    ),
                ),
        )
        .child(caption(
            &theme,
            "Reduced transparency: opaque scroll-edge backing",
        ))
        .child(
            div().h(px(160.0)).overflow_hidden().child(
                crate::layout::ScrollEdgeEffect::new("scene.scroll-edge.hard")
                    .top(true)
                    .hard()
                    .band(64.0)
                    .child(filler(&theme, "The same transcript", 7)),
            ),
        )
        .into_any_element()
}

pub(super) fn grid(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let item = |id: &'static str, label: &'static str| {
        GridItem::new(
            id,
            Card::new()
                .id(format!("scene.grid.card.{id}"))
                .padding(Space::Md)
                .child(crate::foundation::text(&theme, TypeScale::Label, label))
                .child(caption(&theme, "Caller-owned content")),
        )
        .span_at(Breakpoint::Large, if id == "summary" { 2 } else { 1 })
    };
    stack(&theme)
        .w(px(840.0))
        .child(caption(
            &theme,
            "Columns and spans resolve from the grid's measured width while semantic order stays source order",
        ))
        .child(
            Grid::new("scene.grid")
                .columns(1)
                .columns_at(Breakpoint::Medium, 2)
                .columns_at(Breakpoint::Large, 3)
                .gap(Space::Sm)
                .items([
                    item("summary", "Summary"),
                    item("settings", "Settings"),
                    item("history", "History"),
                    item("policy", "Policy"),
                ]),
        )
        .into_any_element()
}

pub(super) fn container(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .child(caption(
            &theme,
            "Page width and gutters come from the installed theme, not component-local pixels",
        ))
        .child(
            Container::new("scene.container.readable")
                .width(ContainerWidth::Readable)
                .child(
                    Card::new()
                        .id("scene.container.readable.card")
                        .padding(Space::Md)
                        .child(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Readable content",
                        )),
                ),
        )
        .child(
            Container::new("scene.container.dialog")
                .width(ContainerWidth::Dialog)
                .child(
                    Card::new()
                        .id("scene.container.dialog.card")
                        .padding(Space::Md)
                        .child(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Dialog-width content",
                        )),
                ),
        )
        .into_any_element()
}

/// A pane with a heading and something under it worth resizing.
///
/// Two panes of the same fixture copy demonstrate a divider and nothing else:
/// what a reader has to be able to judge is that the two sides hold different
/// things and that dragging trades room between them, so each side is given
/// its own kind of content and its own heading.
fn pane(theme: &Theme, title: &'static str, rows: &'static [&'static str]) -> gpui::Div {
    div()
        .column()
        .size_full()
        .min_w_0()
        .overflow_hidden()
        .child(
            div()
                .row()
                .items_center()
                .px(px(theme.space(Space::Md)))
                .py(px(theme.space(Space::Sm)))
                .child(
                    crate::foundation::text(theme, TypeScale::Label, title)
                        .text_tone(theme, TextTone::Muted),
                ),
        )
        .child(
            div()
                .column()
                .gap(px(theme.space(Space::Xs)))
                .px(px(theme.space(Space::Md)))
                .py(px(theme.space(Space::Sm)))
                .children(
                    rows.iter()
                        .map(|row| crate::foundation::text(theme, TypeScale::Body, *row)),
                ),
        )
}

pub(super) fn split_pane(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(620.0))
        .h(px(380.0))
        .child(
            div()
                .h(px(320.0))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    SplitPane::new("scene.split.workspace")
                        .horizontal()
                        .ratio(0.34)
                        .min_sizes(120.0, 200.0)
                        .collapsible(true)
                        .handle_label("Resize the file tree")
                        .start(pane(
                            &theme,
                            "Files",
                            &[
                                "src/",
                                "  main.rs",
                                "  theme.rs",
                                "  layout/",
                                "    split.rs",
                                "    scroll.rs",
                                "Cargo.toml",
                            ],
                        ))
                        .end(pane(
                            &theme,
                            "split.rs",
                            &[
                                "pub struct SplitPane {",
                                "    ratio: f32,",
                                "    min_start: f32,",
                                "    min_end: f32,",
                                "}",
                                "",
                                "// The divider reports the ratio a drag",
                                "// asked for; the caller decides it.",
                            ],
                        ))
                        .on_resize(|_, _, _| {})
                        .on_collapse(|_, _, _| {}),
                ),
        )
        .into_any_element()
}

/// The same region, already scrolled, so the shadow that says there is content
/// above the fold is in a captured image rather than only in a test.
pub(super) fn scroll_shadow(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    crate::layout::scroll_to(
        "scene.scroll.scrolled",
        gpui::point(px(0.0), px(120.0)),
        window,
        cx,
    );
    stack(&theme)
        .w(px(480.0))
        .child(caption(
            &theme,
            "Scrolled to the middle: content is hidden past both ends, so both \
             fade",
        ))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    ScrollArea::new("scene.scroll.scrolled")
                        .label("Run output")
                        .vertical()
                        .height(200.0)
                        .child(filler(&theme, "Output", 20)),
                ),
        )
        .into_any_element()
}

pub(super) fn scroll_area(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(480.0))
        .child(caption(
            &theme,
            "Overflowing: a scrollbar, and the edges fade",
        ))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    ScrollArea::new("scene.scroll.output")
                        .label("Run output")
                        .vertical()
                        .height(200.0)
                        .child(filler(&theme, "Output", 20)),
                ),
        )
        // Nothing overflows here, so no scrollbar is drawn or published and
        // no edge fades. The area is as tall as what it holds rather than a
        // height guessed at and hoped to be enough: a box a few pixels short
        // of its content draws the scrollbar and the fade of the card above
        // while claiming to be the card that does neither.
        .child(caption(&theme, "Fits: no scrollbar, no fade"))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    ScrollArea::new("scene.scroll.summary")
                        .label("Summary")
                        .vertical()
                        .fit_height()
                        .child(filler(&theme, "Summary", 4)),
                ),
        )
        .into_any_element()
}

/// A region scrolled off both ends, so the fade that says there is more in
/// either direction is in a captured image, beside one that hides nothing and
/// therefore fades at neither edge.
pub(super) fn scroll_fade(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    crate::layout::scroll_to(
        "scene.fade.output",
        gpui::point(px(0.0), px(120.0)),
        window,
        cx,
    );
    stack(&theme)
        .w(px(480.0))
        .child(caption(&theme, "Scrolled: content runs past both ends"))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    ScrollFade::new("scene.fade.scrolled")
                        .edges(FadeEdges::vertical())
                        .fit_height()
                        .child(
                            ScrollArea::new("scene.fade.output")
                                .label("Run output")
                                .vertical()
                                .height(200.0)
                                .child(filler(&theme, "Output", 20)),
                        ),
                ),
        )
        // Nothing is hidden here, so no edge fades and the caller says so.
        .child(caption(&theme, "Nothing hidden: no edge fades"))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    ScrollFade::new("scene.fade.settled").fit_height().child(
                        ScrollArea::new("scene.fade.summary")
                            .label("Summary")
                            .vertical()
                            .height(200.0)
                            .child(filler(&theme, "Summary", 7)),
                    ),
                ),
        )
        .into_any_element()
}

/// A complete client titlebar: product content remains clickable inside the
/// drag strip, and platform controls keep their native hit-test identities.
pub(super) fn desktop_titlebar(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(720.0))
        .child(
            div()
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    DesktopTitlebar::new("scene.desktop-titlebar", "Workspace")
                        .subtitle("main.rs")
                        .left(
                            Button::new("scene.desktop-titlebar.workspace")
                                .label("Workspace menu")
                                .ghost()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .right(
                            Badge::new("Connected")
                                .success()
                                .id("scene.desktop-titlebar.status"),
                        )
                        .on_event(|_, _, _| {}),
                )
                .child(div().h(px(112.0)).p_token(&theme, Space::Lg).child(caption(
                    &theme,
                    "Host content is client input; maximize remains native Snap chrome.",
                ))),
        )
        .into_any_element()
}

/// The overflow menu of the toolbar scene, kept across frames.
pub(super) struct SceneToolbar {
    overflow: Entity<Menu>,
}

impl Global for SceneToolbar {}

pub(super) fn toolbar(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneToolbar>() {
        let overflow = cx.new(|cx| {
            Menu::new("scene.toolbar.overflow", window, cx)
                .trigger_icon(Icon::List)
                .trigger_name("More actions")
        });
        cx.set_global(SceneToolbar { overflow });
    }
    let overflow = cx.global::<SceneToolbar>().overflow.clone();
    let theme = cx.theme().clone();

    stack(&theme)
        .w(px(620.0))
        .child(
            Toolbar::new("scene.toolbar.editor")
                .label("Editor actions")
                .group(
                    "history",
                    [
                        ToolbarItem::new(
                            "editor.undo",
                            "Undo",
                            IconButton::new("scene.toolbar.undo", Icon::ArrowLeft, "Undo")
                                .ghost()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .icon(Icon::ArrowLeft)
                        .shortcut("cmd-z"),
                        ToolbarItem::new(
                            "editor.redo",
                            "Redo",
                            IconButton::new("scene.toolbar.redo", Icon::ArrowRight, "Redo")
                                .ghost()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .icon(Icon::ArrowRight),
                    ],
                )
                .group(
                    "view",
                    [ToolbarItem::new(
                        "editor.view",
                        "View",
                        SegmentedControl::new("scene.toolbar.view")
                            .label("View")
                            .segments([
                                Segment::new("code", "Code"),
                                Segment::new("split", "Split"),
                                Segment::new("preview", "Preview"),
                            ])
                            .selected("split")
                            .small()
                            .on_select(|_, _, _| {}),
                    )],
                )
                .spacer()
                .group(
                    "publish",
                    [
                        ToolbarItem::new(
                            "editor.share",
                            "Share",
                            Button::new("scene.toolbar.share")
                                .label("Share")
                                .secondary()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .icon(Icon::Copy),
                        ToolbarItem::new(
                            "editor.publish",
                            "Publish",
                            Button::new("scene.toolbar.publish")
                                .label("Publish")
                                .primary()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .icon(Icon::ArchiveUp),
                        ToolbarItem::new(
                            "editor.archive",
                            "Archive",
                            Button::new("scene.toolbar.archive")
                                .label("Archive")
                                .secondary()
                                .small()
                                .on_click(|_, _| {}),
                        )
                        .icon(Icon::Archive)
                        .disabled(true),
                    ],
                )
                .overflow_after(4)
                .overflow_menu(overflow),
        )
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "The last two actions moved into the overflow menu.",
        ))
        .into_any_element()
}

/// A layout nested three deep, with one leaf collapsed to its rail. The tree
/// is the caller's: every divider reports the ratio it was asked for and moves
/// nothing here.
pub(super) fn split_tree(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let layout = SplitLayout::horizontal(
        "workspace",
        0.26,
        SplitLayout::leaf(SplitPaneSpec::new("files").min_width(140.0)),
        SplitLayout::horizontal(
            "body",
            0.74,
            SplitLayout::vertical(
                "editing",
                0.6,
                SplitLayout::leaf(SplitPaneSpec::new("editor").min_height(90.0)),
                SplitLayout::leaf(SplitPaneSpec::new("terminal").min_height(70.0)),
            ),
            // A collapsed leaf is drawn at its rail, and the divider beside it
            // is not offered: a fixed extent has no ratio to trade.
            SplitLayout::leaf(SplitPaneSpec::new("outline").rail(40.0).collapsed(true)),
        ),
    );

    stack(&theme)
        .w(px(680.0))
        .child(
            div()
                .h(px(340.0))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    SplitTree::new("scene.tree.workspace")
                        .layout(layout)
                        // Each pane holds enough to reach the bottom of the
                        // room the tree gave it: a pane drawn half empty says
                        // the divider above it is in the wrong place, which is
                        // the one thing this scene is not about.
                        .pane("files", filler(&theme, "Files", 13))
                        .pane("editor", filler(&theme, "main.rs", 7))
                        .pane("terminal", filler(&theme, "Terminal", 4))
                        // The rail is narrower than any label, so the collapsed
                        // leaf is drawn as the room it still holds.
                        .pane("outline", div())
                        .on_change(|_, _, _| {}),
                ),
        )
        .child(caption(
            &theme,
            "A divider high in the tree stops where a leaf far below it would \
             run out of room.",
        ))
        .into_any_element()
}

/// A recursive dock topology: tab stacks are leaves of the same split tree,
/// an empty stack remains available, and a collapsed nested stack keeps its
/// rail instead of flattening the caller's arrangement.
pub(super) fn dock_tree(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let topology = DockTopology::vertical(
        "root",
        0.78,
        DockTopology::horizontal(
            "workspace",
            0.28,
            DockTopology::Stack(DockStack::new("source", ["files", "search"]).active("files")),
            DockTopology::horizontal(
                "editing",
                0.9,
                DockTopology::stack("editor", ["main"]),
                DockTopology::Stack(DockStack::new("outline", ["symbols"]).collapsed(true)),
            ),
        ),
        DockTopology::stack("terminal", std::iter::empty::<&str>()),
    );

    stack(&theme)
        .w(px(900.0))
        .child(caption(
            &theme,
            "recursive caller topology; tab groups, empty destinations, rails, and split dividers share existing primitives",
        ))
        .child(
            div()
                .h(px(600.0))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    DockTree::new("scene.dock-tree", topology)
                        .panels([
                            DockPanel::new("files", "Files")
                                .icon(Icon::Folder)
                                .content(filler(&theme, "Workspace", 12)),
                            DockPanel::new("search", "Search")
                                .icon(Icon::Magnifier)
                                .badge("12"),
                            DockPanel::new("main", "main.rs")
                                .icon(Icon::Document)
                                .content(filler(&theme, "fn main()", 15)),
                            DockPanel::new("symbols", "Outline").icon(Icon::List),
                        ])
                        .on_event(|_, _, _| {}),
                ),
        )
        .into_any_element()
}

#[derive(Default)]
struct SceneFloatingDock {
    bounds: Option<gpui::Bounds<f32>>,
    saved: Option<gpui::Bounds<f32>>,
}
impl Global for SceneFloatingDock {}

pub(super) fn dock_floating(_window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::layout::{DockTreeEvent, FloatingDock};
    if !cx.has_global::<SceneFloatingDock>() {
        cx.set_global(SceneFloatingDock::default());
    }
    let theme = cx.theme().clone();
    let bounds = cx
        .global::<SceneFloatingDock>()
        .bounds
        .unwrap_or(gpui::Bounds::new(
            gpui::point(0.36, 0.18),
            gpui::size(0.48, 0.58),
        ));
    stack(&theme).w(px(850.0))
        .child(caption(&theme, "Fixture: drag the floating header or resize corner; arrow keys adjust either. Restore uses caller-saved completed gestures."))
        .child(Button::new("scene.dock-floating.restore").label("Restore saved layout").on_click(|_, cx| {
            let state = cx.global_mut::<SceneFloatingDock>(); state.bounds = state.saved; cx.refresh_windows();
        }))
        .child(div().h(px(500.0)).child(
            DockTree::new("scene.dock-floating", DockTopology::stack("workspace", ["main"]))
                .floating([FloatingDock::new(DockStack::new("inspector", ["details"]), bounds).expect("valid fixture")]).expect("unique fixture identities")
                .panels([
                    DockPanel::new("main", "Workspace").content(filler(&theme, "Caller-owned background", 15)),
                    DockPanel::new("details", "Details").content(filler(&theme, "Floating inspector", 7)),
                ])
                .on_event(|event, _, cx| {
                    match event {
                        DockTreeEvent::FloatingChanged { bounds, finished, .. } => {
                            let state = cx.global_mut::<SceneFloatingDock>(); state.bounds = Some(bounds); if finished { state.saved = Some(bounds); }
                        }
                        DockTreeEvent::FloatingCancelled { .. } => { let state = cx.global_mut::<SceneFloatingDock>(); state.bounds = state.saved; }
                        _ => {}
                    }
                    cx.refresh_windows();
                })
        )).into_any_element()
}

/// A whole application frame: panels in regions, one region collapsed to a
/// rail, one panel the host refuses, and a status bar under all of it.
pub(super) fn ide_shell(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let mut branch = AsyncValue::<SharedString, String>::ready("main@a1b2c3".into());
    branch.refresh();
    branch.fail_refresh("the host is unreachable".into());

    div()
        .column()
        .w(px(900.0))
        .h(px(940.0))
        .bg(theme.colors.canvas)
        .text_color(theme.colors.text)
        .font_family(theme.typography.sans.clone())
        .child(
            div().flex_1().min_h(px(0.0)).child(
                Dock::new("scene.shell")
                    .share(DockRegion::Left, 0.24)
                    // The bottom region holds one refused panel, and a refusal
                    // is a sentence rather than a document: given a third of
                    // the frame it reads as a region that lost its content
                    // instead of as a panel the host would not open.
                    .share(DockRegion::Bottom, 0.17)
                    .panel(
                        DockRegion::Left,
                        DockPanel::new("files", "Files")
                            .icon(Icon::Folder)
                            .content(filler(&theme, "Workspace", 8)),
                    )
                    .panel(
                        DockRegion::Left,
                        DockPanel::new("search", "Search")
                            .icon(Icon::Magnifier)
                            .badge("12"),
                    )
                    .active(DockRegion::Left, "files")
                    .panel(
                        DockRegion::Centre,
                        DockPanel::new("editor", "main.rs")
                            .icon(Icon::Document)
                            .content(filler(&theme, "fn main()", 12)),
                    )
                    .panel(
                        DockRegion::Right,
                        DockPanel::new("outline", "Outline").icon(Icon::List),
                    )
                    .panel(
                        DockRegion::Right,
                        DockPanel::new("history", "History").icon(Icon::GitBranch),
                    )
                    .collapsed(DockRegion::Right, true)
                    .panel(
                        DockRegion::Bottom,
                        DockPanel::new("terminal", "Terminal")
                            .icon(Icon::Terminal)
                            .content(filler(&theme, "$ cargo test", 4)),
                    )
                    .panel(
                        DockRegion::Bottom,
                        // No badge: a panel that cannot list problems cannot
                        // count them either, and one unavailable reason is the
                        // whole claim. Gluing "there is nothing here" onto it
                        // would report Empty and Unavailable at once.
                        DockPanel::new("problems", "Problems")
                            .icon(Icon::Danger)
                            .unavailable(
                                "The language server is not running, so problems cannot be listed.",
                            ),
                    )
                    // The refused panel is the one on top, because a refusal
                    // nobody can see is a refusal nobody was told about.
                    .active(DockRegion::Bottom, "problems")
                    .on_event(|_, _, _| {}),
            ),
        )
        .child(
            StatusBar::new("scene.shell.status")
                .label("Workspace status")
                .start([
                    StatusItem::text("branch", "main")
                        .icon(Icon::GitBranch)
                        .tracking(&branch),
                    StatusItem::state("build", "Build passing", Tone::Success),
                ])
                .centre([StatusItem::progress("index", "Indexing the workspace")
                    .count(7, 12)
                    .state_name("loading")])
                .end([
                    StatusItem::text("position", "Ln 42, Col 7"),
                    StatusItem::action("encoding", "UTF-8").on_click(|_, _| {}),
                ]),
        )
        .into_any_element()
}

pub(super) fn aspect_ratio(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    // The ratio is the subject, so the block it drives is drawn the way every
    // other bounded region in the library is: a surface step, the card radius,
    // and its label inside it.
    let filled = |label: &'static str| {
        div()
            .size_full()
            .flex()
            .items_center()
            .justify_center()
            .surface(&theme, Surface::Sunken)
            .radius(&theme, Radius::Card)
            .overflow_hidden()
            .child(
                crate::foundation::text(&theme, TypeScale::Label, label)
                    .text_tone(&theme, TextTone::Muted),
            )
    };
    stack(&theme)
        .child(caption(&theme, "Width given, height from the ratio"))
        .child(
            div().w(px(320.0)).child(
                AspectRatio::of("scene.aspect.wide", 16.0, 9.0)
                    .width_driven()
                    .child(filled("16 by 9")),
            ),
        )
        .child(caption(&theme, "Height given, width from the ratio"))
        .child(
            div().h(px(120.0)).child(
                AspectRatio::new("scene.aspect.square", 1.0)
                    .height_driven()
                    .child(filled("square")),
            ),
        )
        .into_any_element()
}

/// A container that arranges itself from its own measured width.
///
/// Both arrangements are shown at once, because the point of the component is
/// that the caller decides them and neither is a fallback for the other. The
/// unmeasured first frame is a state a still image cannot hold, so it is named
/// rather than drawn.
pub(super) fn responsive(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let arrangement = move |id: &'static str| {
        let theme = theme.clone();
        move |size: ContainerSize, _: &mut Window, cx: &mut App| {
            let wide = size.width().is_some_and(|measured| measured >= 480.0);
            // The unmeasured frame is a real state and it is named rather than
            // guessed, which is the whole argument the component makes.
            let heading = match size.width() {
                None => "Not laid out yet, so neither arrangement is chosen".to_string(),
                Some(measured) if wide => format!("{measured:.0}px wide, so two columns"),
                Some(measured) => format!("{measured:.0}px wide, so one column"),
            };
            // A card rather than a tinted rectangle, and with enough in it to
            // be a column: two panels holding one word each say nothing about
            // an arrangement, which is what the component is for.
            let block = |label: &'static str, lines: &'static [&'static str], cx: &mut App| {
                div()
                    .flex_1()
                    .min_w_0()
                    .column()
                    .gap_token(&theme, Space::Xs)
                    .p_token(&theme, Space::Md)
                    .card_surface(&theme, CardVariant::Elevated)
                    .child(crate::foundation::text(&theme, TypeScale::Label, label))
                    .children(lines.iter().map(|line| {
                        crate::foundation::text(&theme, TypeScale::Caption, *line)
                            .text_tone(&theme, TextTone::Muted)
                    }))
                    .semantic_in(
                        cx,
                        NodeSpec::new(format!("{id}.{}", label.to_lowercase()), Role::Group)
                            .text(label),
                    )
            };
            let panes = div()
                .gap_token(&theme, Space::Sm)
                .child(block(
                    "Settings",
                    &["Theme", "Density", "Reading direction"],
                    cx,
                ))
                .child(block(
                    "Detail",
                    &["Studio Dark", "Comfortable", "Left to right"],
                    cx,
                ));
            div()
                .column()
                .gap_token(&theme, Space::Sm)
                .child(
                    crate::foundation::text(&theme, TypeScale::Caption, heading.clone())
                        .text_tone(&theme, TextTone::Faint),
                )
                .child(if wide { panes.row() } else { panes.column() })
                .semantic_in(cx, NodeSpec::new(id, Role::Group).value(heading))
                .into_any_element()
        }
    };

    let theme = cx.theme().clone();
    stack(&theme)
        .child(caption(&theme, "Wide enough for two columns"))
        .child(div().w(px(620.0)).child(Responsive::new(
            "scene.responsive.wide",
            arrangement("scene.responsive.wide.body"),
        )))
        .child(caption(&theme, "The same content, narrow"))
        .child(div().w(px(320.0)).child(Responsive::new(
            "scene.responsive.narrow",
            arrangement("scene.responsive.narrow.body"),
        )))
        .into_any_element()
}

//! Time-based and dimensional media.

use super::support::*;

/// The cube the model scenes read: a glTF document with its own buffer
/// inside it, so the reader is exercised on the path a host would take and
/// nothing is fetched to draw a scene.
pub(super) const CUBE: &[u8] = include_bytes!("../../assets/models/cube.gltf");

/// A measured envelope, in the shape a host that read the samples would hand
/// over. It is written down rather than sampled, because a scene that drew a
/// different waveform every run could not be a visual baseline.
pub(super) fn scene_peaks(count: usize) -> Vec<f32> {
    (0..count)
        .map(|index| {
            let phase = index as f32 / count as f32;
            let swell = (phase * 11.0).sin() * 0.32 + (phase * 3.0).cos() * 0.28;
            (0.42 + swell).clamp(0.05, 1.0)
        })
        .collect()
}

pub(super) fn audio_player(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(392.0))
        .child(
            AudioPlayer::new("scene.audio.ready")
                .control_size(gpui_kit_theme::ControlSize::Touch)
                .title("Release walkthrough")
                .subtitle("Recorded 12 March")
                .transport(
                    FixtureTransport::ready(240.0)
                        .position(72.0)
                        .state(TransportState::Playing)
                        .volume(0.7)
                        .buffered([BufferedRange::new(0.0, 156.0)])
                        .shared(),
                )
                .elapsed("01:12")
                .remaining("-02:48")
                .peaks(scene_peaks(96))
                .speeds([1.0, 1.5, 2.0])
                .step_seconds(10.0)
                .on_event(|_, _, _| {}),
        )
        .child(
            Divider::new()
                .id("scene.audio.rule.backend")
                .label("Nothing here can decode it"),
        )
        .child(
            AudioPlayer::new("scene.audio.absent")
                .title("interview.opus")
                .transport(
                    FixtureTransport::new()
                        .no_backend("There is no Opus decoder on this machine.")
                        .shared(),
                )
                .on_event(|_, _, _| {}),
        )
        .child(
            Divider::new()
                .id("scene.audio.rule.none")
                .label("No player connected at all"),
        )
        .child(
            AudioPlayer::new("scene.audio.none")
                .title("Standup recording")
                .on_event(|_, _, _| {}),
        )
        .into_any_element()
}

fn walkthrough_frame(cx: &App) -> AnyElement {
    let theme = cx.theme().clone();
    let metric = |label: &'static str, value: &'static str| {
        div()
            .flex_1()
            .min_w_0()
            .column()
            .gap_token(&theme, Space::Xs)
            .p_token(&theme, Space::Sm)
            .radius(&theme, Radius::Small)
            .bg(theme.colors.raised)
            .child(
                crate::foundation::text(&theme, TypeScale::Caption, label)
                    .text_tone(&theme, TextTone::Muted),
            )
            .child(crate::foundation::text(&theme, TypeScale::Subtitle, value))
    };
    div()
        .size_full()
        .column()
        .bg(theme.colors.panel)
        .child(
            div()
                .row()
                .items_center()
                .gap_token(&theme, Space::Sm)
                .px_token(&theme, Space::Md)
                .py_token(&theme, Space::Sm)
                .surface(&theme, Surface::Raised)
                .child(div().size(px(7.0)).rounded_full().bg(theme.colors.success))
                .child(crate::foundation::text(
                    &theme,
                    TypeScale::Label,
                    "Release command center",
                )),
        )
        .child(
            div()
                .flex_1()
                .min_h_0()
                .row()
                .child(
                    div()
                        .w(px(76.0))
                        .h_full()
                        .flex_none()
                        .column()
                        .gap_token(&theme, Space::Sm)
                        .p_token(&theme, Space::Sm)
                        .bg(theme.colors.sunken)
                        .children([16.0, 34.0, 26.0, 42.0].map(|width| {
                            div()
                                .w(px(width))
                                .h(px(theme.borders.thick))
                                .rounded_full()
                                .bg(theme.colors.hairline_strong)
                        })),
                )
                .child(
                    div()
                        .flex_1()
                        .min_w_0()
                        .column()
                        .gap_token(&theme, Space::Sm)
                        .p_token(&theme, Space::Md)
                        .child(
                            div()
                                .row()
                                .gap_token(&theme, Space::Sm)
                                .child(metric("Checks", "48 / 52"))
                                .child(metric("Coverage", "92%"))
                                .child(metric("Elapsed", "04:18")),
                        )
                        .child(
                            div()
                                .column()
                                .gap_token(&theme, Space::Xs)
                                .p_token(&theme, Space::Sm)
                                .radius(&theme, Radius::Small)
                                .surface(&theme, Surface::Raised)
                                .child(crate::foundation::text(
                                    &theme,
                                    TypeScale::Caption,
                                    "Verification progress",
                                ))
                                .child(
                                    div()
                                        .h(px(6.0))
                                        .w_full()
                                        .rounded_full()
                                        .overflow_hidden()
                                        .bg(theme.colors.track)
                                        .child(
                                            div()
                                                .h_full()
                                                .w(px(218.0))
                                                .rounded_full()
                                                .bg(theme.colors.accent),
                                        ),
                                ),
                        ),
                ),
        )
        .into_any_element()
}

fn walkthrough_poster(cx: &App) -> AnyElement {
    let theme = cx.theme().clone();
    div()
        .size_full()
        .flex()
        .flex_col()
        .items_center()
        .justify_center()
        .gap_token(&theme, Space::Sm)
        .bg(theme.colors.sunken)
        .child(
            div()
                .size(px(46.0))
                .flex()
                .items_center()
                .justify_center()
                .rounded_full()
                .frame(&theme, Surface::Raised, Elevation::Raised)
                .child(
                    gpui_kit_assets::icon(Icon::Play)
                        .size(px(theme.control.md.icon_size))
                        .text_color(theme.colors.accent_strong),
                ),
        )
        .child(crate::foundation::text(
            &theme,
            TypeScale::Subtitle,
            "Release walkthrough",
        ))
        .child(
            crate::foundation::text(&theme, TypeScale::Caption, "4 minutes · 8 chapters")
                .text_tone(&theme, TextTone::Muted),
        )
        .into_any_element()
}

pub(super) fn video_player(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(392.0))
        .child(
            VideoPlayer::new("scene.video.frame")
                .control_size(gpui_kit_theme::ControlSize::Touch)
                .presentation(
                    crate::content::transport::MediaPresentation::Inline,
                    "Present video",
                )
                .presentation_refused("Fixture host: native video presentation is unavailable.")
                .on_presentation_request(|_, _, _| {})
                .title("Screen capture")
                .transport(
                    FixtureTransport::ready(96.0)
                        .position(24.0)
                        .state(TransportState::Playing)
                        .volume(0.6)
                        .shared(),
                )
                .frame(|_, cx| Some(walkthrough_frame(cx)))
                .elapsed("00:24")
                .remaining("-01:12")
                .on_event(|_, _, _| {}),
        )
        .child(
            Divider::new()
                .id("scene.video.rule.poster")
                .label("Poster while the first frame is still unavailable"),
        )
        .child(
            VideoPlayer::new("scene.video.poster")
                .title("Release walkthrough")
                .transport(FixtureTransport::ready(240.0).shared())
                .poster(|_, cx| Some(walkthrough_poster(cx)))
                .elapsed("00:00")
                .remaining("-04:00")
                .on_event(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn model_viewer(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let read = || ModelState::read(CUBE, ModelBounds::default());
    stack(&theme)
        .w(px(720.0))
        .child(
            div()
                .row()
                .items_start()
                .gap(px(theme.spacing.lg))
                .child(
                    div().flex_1().min_w_0().child(
                        ModelViewer::new("scene.model.flat")
                            .title("cube.gltf")
                            .state(read())
                            .orbit(0.62, 0.42)
                            .height(240.0)
                            .on_event(|_, _, _| {}),
                    ),
                )
                .child(
                    div().flex_1().min_w_0().child(
                        ModelViewer::new("scene.model.wireframe")
                            .title("cube.gltf")
                            .state(read())
                            .shading(ModelShading::Wireframe)
                            .orbit(0.62, 0.42)
                            .height(240.0)
                            .on_event(|_, _, _| {}),
                    ),
                ),
        )
        .child(
            Divider::new()
                .id("scene.model.rule.refused")
                .label("A document past a bound"),
        )
        .child(
            ModelViewer::new("scene.model.refused")
                .title("city-block.glb")
                .rejected(ModelError::TooLarge {
                    limit: ModelLimit::Triangles,
                    found: 412_930,
                    allowed: 24_576,
                })
                .height(160.0)
                .on_event(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn audio_waveform(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(caption(
            &theme,
            "already-normalized peaks; a missing playhead draws no played split",
        ))
        .child(
            AudioWaveform::new("scene.waveform.ready")
                .peaks(scene_peaks(48))
                .playhead(0.38),
        )
        .child(AudioWaveform::new("scene.waveform.loading").state(AudioWaveformState::Loading))
        .child(AudioWaveform::new("scene.waveform.empty").state(AudioWaveformState::Empty))
        .child(AudioWaveform::new("scene.waveform.unavailable").state(
            AudioWaveformState::Unavailable("The sampler host refused the request.".into()),
        ))
        .child(
            AudioWaveform::new("scene.waveform.error").state(AudioWaveformState::Error(
                "The sampler request failed.".into(),
            )),
        )
        .into_any_element()
}

//! One canonical rendering per component, grouped the way the components are.
//!
//! A scene is the single description of a component's states that both the
//! gallery and the audit tests consume, so a component cannot be reviewed
//! visually in one arrangement and tested in another.
//!
//! Almost every scene is an *exhibit*: it is about one component, it lives in
//! the file named after that component's family, and it is where a reader is
//! sent to review it. `xtask api check` fails when a public component has no
//! exhibit, so a component cannot be added and left unseen.
//!
//! The rest are *compositions*, and there are three of them. A composition is
//! built the way a product would build one, because components interact in
//! ways none of them shows alone. It is nobody's coverage, and [`Shows`]
//! records the difference so the catalog does not claim a component was
//! reviewed when it was only recognised inside a shell.
//!
//! What is deliberately not here is a scene per screenshot. The files below
//! are the component families, not a taxonomy invented for this module, so a
//! component and the rendering that reviews it move together.

mod support;

mod agent;
mod canvas;
mod cartesian;
mod compositions;
mod content;
mod controls;
mod data;
#[cfg(feature = "fixtures")]
mod datetime;
mod display;
mod effects;
mod flip_configuration;
mod game;
mod geography;
mod heatmap;
mod interaction;
mod layout;
mod media;
mod motion;
mod navigation;
mod overlay;
mod plot;
mod specialized;
mod structured;

use gpui::{AnyElement, App, Window};

use crate::foundation::direction::LayoutDirection;

use agent::{
    agent_avatar, agent_plan, agent_roster, agent_run_canvas, agent_run_issues, approval,
    artifact_preview, clarification, cost_meter, feedback_rating, offering_catalog,
    permission_matrix, persona, prompt_builder, server_list, thinking, tool_call,
};
use canvas::{
    canvas_regions, canvas_tools, node_graph, node_graph_layout, node_graph_motion,
    node_graph_routing,
};
use cartesian::{
    cartesian, cartesian_dense, cartesian_layout, cartesian_lifecycle, cartesian_linked,
    cartesian_states,
};
use compositions::{motion_flip, motion_state, reading_direction};
#[cfg(all(feature = "terminal", not(target_family = "wasm")))]
use content::terminal;
use content::{
    agent_document, browser_panel, code_view, conversation, conversation_growing, diff_view,
    image_viewer, log_stream, markdown, outline, transport,
};
use controls::{
    actions, auth_sign_in, auth_verification, button, cascader, choice, color_picker, copy_button,
    dropzone, editor, editor_folding, editor_multicursor, editor_options, editor_services,
    filter_bar, find_replace, form, inline_edit, input, keybinding, keymap_editor, mention_input,
    multi_select, rich_text_editor, search_field, search_input, settings, settings_page, textarea,
    toggle, touch_inputs, touch_pickers, touch_pickers_open, transfer_list, translation_packs,
    upload_list,
};
use data::{
    data_grid, data_grid_editing, deferred_drop, diagnostics_list, drag_list, drag_tree, flow,
    image_list, kanban, list, masonry, table, tree, tree_grid,
};
#[cfg(feature = "fixtures")]
use datetime::{calendar, date_range, date_time, touch_dates};
use display::{
    animated_number, attachment, avatar, badge, banner, bubble, card, chart, detail, divider,
    empty_state, failure_panel, heatmap, icon, loading, metric_card, outcome_panel,
    performance_hud, plot, progress_bar, progress_circle, rating, sparkline, stage_progress,
    state_ladder, status, tag, trace, trace_time,
};
use effects::{cinematic_effects, visual_effects};
use flip_configuration::flip_configuration;
use game::game_ui;
use geography::{geography, geography_scale};
use heatmap::{continuous_heatmap, continuous_heatmap_transition, heatmap_reordering};
use interaction::{pull_to_refresh, swipe_actions};
use layout::{
    aspect_ratio, container, desktop_titlebar, dock_floating, dock_tree, grid, ide_shell,
    mobile_page, responsive, scroll_area, scroll_edge_effect, scroll_fade, scroll_shadow,
    split_pane, split_tree, toolbar,
};
use media::{audio_player, audio_waveform, model_viewer, video_player};
use motion::{micro, motion_primitives};
use navigation::{
    accordion, adaptive_navigation, anchor_list, bottom_navigation, breadcrumb, carousel,
    collapsible, document_tabs, nav_back_preview, nav_stack, pagination, sidebar, sidebar_overflow,
    tabs, undo_history, wizard,
};
use overlay::{
    action_sheet, bottom_sheet, command_palette, context_menu, dialog, drawer, frost, glass,
    glass_materials, glass_optics, hover_card, kbd, media_caption, menu, menubar,
    notification_center, overlay, popover, toast, tooltip,
};
use plot::{raw_candlestick, sankey_layout, sankey_motion};
use specialized::{specialized, specialized_distribution, specialized_exploration};
use structured::{json_view, schema_form};

/// What a rendering exists to show.
///
/// The distinction is the difference between reviewing a component and
/// recognising it. A component that has only ever been seen inside a shell has
/// been recognised; nobody has looked at its states, and the catalog should
/// not claim otherwise, which is why only [`Shows::Subjects`] counts as
/// coverage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shows {
    /// The components this rendering is *about*. It arranges them so their
    /// states can be compared, and it is where a reader is sent to review
    /// them. Everything else it draws is scaffolding.
    Subjects(&'static [&'static str]),
    /// An arrangement the way a product would build one, kept because
    /// components interact in ways none of them shows alone. It is nobody's
    /// coverage: every component it draws is reviewed somewhere else.
    Composition(&'static [&'static str]),
}

impl Shows {
    /// Every component named, whichever kind this is.
    pub fn components(&self) -> &'static [&'static str] {
        match self {
            Self::Subjects(names) | Self::Composition(names) => names,
        }
    }

    /// The components this rendering is the review of, which is empty for a
    /// composition.
    pub fn subjects(&self) -> &'static [&'static str] {
        match self {
            Self::Subjects(names) => names,
            Self::Composition(_) => &[],
        }
    }
}

/// One canonical rendering, addressed by name.
pub struct Scene {
    pub name: &'static str,
    pub build: fn(&mut Window, &mut App) -> AnyElement,
    /// What this rendering is for, declared rather than inferred.
    ///
    /// It used to be read back out of the source by following every helper a
    /// scene called, which answered a different question — *what types does
    /// this code path touch* — and answered it as an upper bound. Three
    /// unrelated scenes that shared one fixture helper reported the same seven
    /// components, so "which scene shows me" was a claim no reader could rely
    /// on. Declaring it makes it exact, and `xtask api check` holds the
    /// declaration to what the source can actually reach.
    pub shows: Shows,
}

impl std::fmt::Debug for Scene {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("Scene")
            .field("name", &self.name)
            .field("shows", &self.shows)
            .finish()
    }
}

/// Every scene, in a stable order.
pub fn catalog() -> Vec<Scene> {
    #[allow(unused_mut)]
    let mut scenes = vec![
        Scene {
            name: "bottom-sheet",
            build: bottom_sheet,
            shows: Shows::Subjects(&["BottomSheet"]),
        },
        Scene {
            name: "action-sheet",
            build: action_sheet,
            shows: Shows::Subjects(&["ActionSheet"]),
        },
        Scene {
            name: "pull-to-refresh",
            build: pull_to_refresh,
            shows: Shows::Subjects(&["PullToRefresh"]),
        },
        Scene {
            name: "swipe-actions",
            build: swipe_actions,
            shows: Shows::Subjects(&["SwipeActions"]),
        },
        Scene {
            name: "button",
            build: button,
            shows: Shows::Subjects(&["Button"]),
        },
        Scene {
            name: "badge",
            build: badge,
            shows: Shows::Subjects(&["Badge"]),
        },
        Scene {
            name: "card",
            build: card,
            shows: Shows::Subjects(&["Card", "ListRow"]),
        },
        Scene {
            name: "rating",
            build: rating,
            shows: Shows::Subjects(&["Rating"]),
        },
        Scene {
            name: "bubble",
            build: bubble,
            shows: Shows::Subjects(&["Bubble"]),
        },
        Scene {
            name: "status",
            build: status,
            shows: Shows::Subjects(&["Callout", "StatusDot", "StatusLine"]),
        },
        Scene {
            name: "loading",
            build: loading,
            shows: Shows::Subjects(&[
                "BarLoader",
                "LoadMore",
                "PulseLoader",
                "RefreshVeil",
                "Skeleton",
                "Spinner",
            ]),
        },
        Scene {
            name: "visual-effects",
            build: visual_effects,
            shows: Shows::Subjects(&["EffectParticles"]),
        },
        Scene {
            name: "cinematic-effects",
            build: cinematic_effects,
            shows: Shows::Subjects(&["CinematicEffect"]),
        },
        Scene {
            name: "choice",
            build: choice,
            shows: Shows::Subjects(&["Checkbox", "Radio", "Slider", "Switch"]),
        },
        Scene {
            name: "multi-select",
            build: multi_select,
            shows: Shows::Subjects(&["MultiSelect"]),
        },
        Scene {
            name: "transfer-list",
            build: transfer_list,
            shows: Shows::Subjects(&["TransferList"]),
        },
        Scene {
            name: "input",
            build: input,
            shows: Shows::Subjects(&["Select", "TextInput"]),
        },
        Scene {
            name: "touch-inputs",
            build: touch_inputs,
            shows: Shows::Subjects(&["TextInput", "TextArea", "PasswordInput", "OneTimeCodeInput"]),
        },
        Scene {
            name: "touch-pickers",
            build: touch_pickers,
            shows: Shows::Subjects(&["Select", "Combobox", "MultiSelect"]),
        },
        Scene {
            name: "touch-pickers-open",
            build: touch_pickers_open,
            shows: Shows::Subjects(&["Combobox"]),
        },
        Scene {
            name: "textarea",
            build: textarea,
            shows: Shows::Subjects(&["TextArea"]),
        },
        Scene {
            name: "editor",
            build: editor,
            shows: Shows::Subjects(&["Editor"]),
        },
        Scene {
            name: "editor-multicursor",
            build: editor_multicursor,
            shows: Shows::Subjects(&["Editor"]),
        },
        Scene {
            name: "editor-services",
            build: editor_services,
            shows: Shows::Subjects(&["Editor"]),
        },
        Scene {
            name: "editor-folding",
            build: editor_folding,
            shows: Shows::Subjects(&["Editor"]),
        },
        Scene {
            name: "editor-options",
            build: editor_options,
            shows: Shows::Subjects(&["Editor", "TextArea", "RichTextEditor"]),
        },
        Scene {
            name: "mention-input",
            build: mention_input,
            shows: Shows::Subjects(&["MentionInput"]),
        },
        Scene {
            name: "rich-text-editor",
            build: rich_text_editor,
            shows: Shows::Subjects(&["RichTextEditor"]),
        },
        Scene {
            name: "form",
            build: form,
            shows: Shows::Subjects(&[
                "Combobox",
                "FormField",
                "NumberInput",
                "SegmentedControl",
                "TagInput",
            ]),
        },
        Scene {
            name: "auth-sign-in",
            build: auth_sign_in,
            shows: Shows::Subjects(&["PasswordInput"]),
        },
        Scene {
            name: "auth-verification",
            build: auth_verification,
            shows: Shows::Subjects(&["OneTimeCodeInput"]),
        },
        Scene {
            name: "actions",
            build: actions,
            shows: Shows::Subjects(&["ButtonGroup", "IconButton", "SplitButton"]),
        },
        Scene {
            name: "progress-bar",
            build: progress_bar,
            shows: Shows::Subjects(&["ProgressBar"]),
        },
        Scene {
            name: "state-ladder",
            build: state_ladder,
            shows: Shows::Subjects(&["StateView", "StaleMark"]),
        },
        Scene {
            name: "banner",
            build: banner,
            shows: Shows::Subjects(&["Banner"]),
        },
        Scene {
            name: "outcome-panel",
            build: outcome_panel,
            shows: Shows::Subjects(&["OutcomePanel"]),
        },
        Scene {
            name: "stage-progress",
            build: stage_progress,
            shows: Shows::Subjects(&["StageProgress"]),
        },
        Scene {
            name: "divider",
            build: divider,
            shows: Shows::Subjects(&["Divider"]),
        },
        Scene {
            name: "tag",
            build: tag,
            shows: Shows::Subjects(&["Tag"]),
        },
        Scene {
            name: "avatar",
            build: avatar,
            shows: Shows::Subjects(&["Avatar", "AvatarGroup"]),
        },
        Scene {
            name: "empty-state",
            build: empty_state,
            shows: Shows::Subjects(&["EmptyState"]),
        },
        Scene {
            name: "kbd",
            build: kbd,
            shows: Shows::Subjects(&["Kbd"]),
        },
        Scene {
            name: "overlay",
            build: overlay,
            shows: Shows::Subjects(&["Overlay"]),
        },
        Scene {
            name: "dialog",
            build: dialog,
            shows: Shows::Subjects(&["Dialog"]),
        },
        Scene {
            name: "tooltip",
            build: tooltip,
            shows: Shows::Subjects(&["Tooltip"]),
        },
        Scene {
            name: "menu",
            build: menu,
            shows: Shows::Subjects(&["Menu"]),
        },
        Scene {
            name: "context-menu",
            build: context_menu,
            shows: Shows::Subjects(&["ContextMenu"]),
        },
        Scene {
            name: "popover",
            build: popover,
            shows: Shows::Subjects(&["Popover"]),
        },
        Scene {
            name: "command-palette",
            build: command_palette,
            shows: Shows::Subjects(&["CommandPalette"]),
        },
        Scene {
            name: "toast",
            build: toast,
            shows: Shows::Subjects(&["ToastLayer"]),
        },
        Scene {
            name: "tabs",
            build: tabs,
            shows: Shows::Subjects(&["Tabs"]),
        },
        Scene {
            name: "carousel",
            build: carousel,
            shows: Shows::Subjects(&["Carousel"]),
        },
        Scene {
            name: "nav-stack",
            build: nav_stack,
            shows: Shows::Subjects(&["NavStack"]),
        },
        Scene {
            name: "accordion",
            build: accordion,
            shows: Shows::Subjects(&["Accordion"]),
        },
        Scene {
            name: "breadcrumb",
            build: breadcrumb,
            shows: Shows::Subjects(&["Breadcrumb"]),
        },
        Scene {
            name: "list",
            build: list,
            shows: Shows::Subjects(&["List"]),
        },
        Scene {
            name: "image-list",
            build: image_list,
            shows: Shows::Subjects(&["ImageList"]),
        },
        Scene {
            name: "masonry",
            build: masonry,
            shows: Shows::Subjects(&["Masonry"]),
        },
        Scene {
            name: "flow",
            build: flow,
            shows: Shows::Subjects(&["Flow"]),
        },
        Scene {
            name: "table",
            build: table,
            shows: Shows::Subjects(&["Table"]),
        },
        Scene {
            name: "data-grid",
            build: data_grid,
            shows: Shows::Subjects(&["BulkBar", "DataGrid"]),
        },
        Scene {
            name: "data-grid-editing",
            build: data_grid_editing,
            shows: Shows::Subjects(&["DataGrid"]),
        },
        Scene {
            name: "tree-grid",
            build: tree_grid,
            shows: Shows::Subjects(&["TreeGrid"]),
        },
        Scene {
            name: "tree",
            build: tree,
            shows: Shows::Subjects(&["Tree"]),
        },
        Scene {
            name: "split-pane",
            build: split_pane,
            shows: Shows::Subjects(&["SplitPane"]),
        },
        Scene {
            name: "grid",
            build: grid,
            shows: Shows::Subjects(&["Grid"]),
        },
        Scene {
            name: "container",
            build: container,
            shows: Shows::Subjects(&["Container"]),
        },
        Scene {
            name: "scroll-area",
            build: scroll_area,
            shows: Shows::Subjects(&["ScrollArea"]),
        },
        Scene {
            name: "scroll-shadow",
            build: scroll_shadow,
            shows: Shows::Subjects(&["ScrollArea"]),
        },
        Scene {
            name: "scroll-fade",
            build: scroll_fade,
            shows: Shows::Subjects(&["ScrollFade"]),
        },
        Scene {
            name: "scroll-edge-effect",
            build: scroll_edge_effect,
            shows: Shows::Subjects(&["ScrollEdgeEffect"]),
        },
        Scene {
            name: "frost",
            build: frost,
            shows: Shows::Subjects(&["Frost"]),
        },
        Scene {
            name: "glass",
            build: glass,
            shows: Shows::Subjects(&["Glass"]),
        },
        Scene {
            name: "glass-optics",
            build: glass_optics,
            shows: Shows::Subjects(&["Glass", "GlassGroup"]),
        },
        Scene {
            name: "glass-materials",
            build: glass_materials,
            shows: Shows::Subjects(&["Glass", "GlassGroup"]),
        },
        Scene {
            name: "media-caption",
            build: media_caption,
            shows: Shows::Composition(&["Button"]),
        },
        Scene {
            name: "toolbar",
            build: toolbar,
            shows: Shows::Subjects(&["Toolbar"]),
        },
        Scene {
            name: "desktop-titlebar",
            build: desktop_titlebar,
            shows: Shows::Subjects(&["DesktopTitlebar"]),
        },
        Scene {
            name: "mobile-page",
            build: mobile_page,
            shows: Shows::Subjects(&["AppBar", "PageLayout"]),
        },
        Scene {
            name: "bottom-navigation",
            build: bottom_navigation,
            shows: Shows::Subjects(&["BottomNavigation"]),
        },
        Scene {
            name: "nav-back-preview",
            build: nav_back_preview,
            shows: Shows::Subjects(&["NavStack"]),
        },
        Scene {
            name: "adaptive-navigation",
            build: adaptive_navigation,
            shows: Shows::Composition(&[
                "Responsive",
                "Sidebar",
                "BottomNavigation",
                "PageLayout",
                "AppBar",
            ]),
        },
        Scene {
            name: "sidebar",
            build: sidebar,
            shows: Shows::Subjects(&["Sidebar"]),
        },
        Scene {
            name: "sidebar-overflow",
            build: sidebar_overflow,
            shows: Shows::Subjects(&["Sidebar"]),
        },
        Scene {
            name: "pagination",
            build: pagination,
            shows: Shows::Subjects(&["Pagination"]),
        },
        Scene {
            name: "drawer",
            build: drawer,
            shows: Shows::Subjects(&["Drawer"]),
        },
        Scene {
            name: "motion-flip",
            build: motion_flip,
            shows: Shows::Composition(&["Button", "Card", "ListRow"]),
        },
        Scene {
            name: "flip-configuration",
            build: flip_configuration,
            shows: Shows::Composition(&["Button"]),
        },
        Scene {
            name: "motion-state",
            build: motion_state,
            shows: Shows::Composition(&[
                "Accordion",
                "Button",
                "Checkbox",
                "ProgressBar",
                "Radio",
                "SegmentedControl",
                "Switch",
                "Tabs",
            ]),
        },
        Scene {
            name: "motion-primitives",
            build: motion_primitives,
            shows: Shows::Composition(&["Button", "Tabs"]),
        },
        Scene {
            name: "animated-number",
            build: animated_number,
            shows: Shows::Subjects(&["AnimatedNumber"]),
        },
        Scene {
            name: "drag-list",
            build: drag_list,
            shows: Shows::Subjects(&["List"]),
        },
        Scene {
            name: "deferred-drop",
            build: deferred_drop,
            shows: Shows::Composition(&["List", "Tabs", "Tree", "Button"]),
        },
        Scene {
            name: "drag-tree",
            build: drag_tree,
            shows: Shows::Subjects(&["Tree"]),
        },
        Scene {
            name: "dropzone",
            build: dropzone,
            shows: Shows::Subjects(&["Dropzone"]),
        },
        Scene {
            name: "wizard",
            build: wizard,
            shows: Shows::Subjects(&["Wizard"]),
        },
        Scene {
            name: "undo-history",
            build: undo_history,
            shows: Shows::Subjects(&["UndoHistory"]),
        },
        Scene {
            name: "settings",
            build: settings,
            shows: Shows::Subjects(&["SettingsList", "SettingsRow", "SettingsSection"]),
        },
        Scene {
            name: "settings-page",
            build: settings_page,
            shows: Shows::Subjects(&["SettingsList"]),
        },
        Scene {
            name: "translation-packs",
            build: translation_packs,
            shows: Shows::Composition(&["Card", "Button"]),
        },
        Scene {
            name: "detail",
            build: detail,
            shows: Shows::Subjects(&["DescriptionList", "Timeline"]),
        },
        Scene {
            name: "filter-bar",
            build: filter_bar,
            shows: Shows::Subjects(&["FilterBar"]),
        },
        Scene {
            name: "inline-edit",
            build: inline_edit,
            shows: Shows::Subjects(&["InlineEdit"]),
        },
        Scene {
            name: "progress-circle",
            build: progress_circle,
            shows: Shows::Subjects(&["ProgressCircle"]),
        },
        Scene {
            name: "split-tree",
            build: split_tree,
            shows: Shows::Subjects(&["SplitTree"]),
        },
        Scene {
            name: "dock-tree",
            build: dock_tree,
            shows: Shows::Subjects(&["DockTree"]),
        },
        Scene {
            name: "dock-floating",
            build: dock_floating,
            shows: Shows::Subjects(&["DockTree"]),
        },
        Scene {
            name: "ide-shell",
            build: ide_shell,
            shows: Shows::Subjects(&["Dock", "StatusBar"]),
        },
        Scene {
            name: "keybinding",
            build: keybinding,
            shows: Shows::Subjects(&["KeybindingRecorder"]),
        },
        Scene {
            name: "keymap-editor",
            build: keymap_editor,
            shows: Shows::Subjects(&["KeymapEditor"]),
        },
        Scene {
            name: "markdown",
            build: markdown,
            shows: Shows::Subjects(&["Markdown"]),
        },
        Scene {
            name: "agent-document",
            build: agent_document,
            shows: Shows::Subjects(&["AgentDocument"]),
        },
        Scene {
            name: "agent-roster",
            build: agent_roster,
            shows: Shows::Subjects(&["AgentCard", "AgentGroup", "AgentRoster", "SubagentTree"]),
        },
        Scene {
            name: "persona",
            build: persona,
            shows: Shows::Subjects(&["PersonaDialogue", "PersonaPortrait", "VoiceReactive"]),
        },
        Scene {
            name: "game-ui",
            build: game_ui,
            shows: Shows::Subjects(&[
                "AbilityBar",
                "ObjectiveTracker",
                "PartyRoster",
                "RewardReveal",
            ]),
        },
        Scene {
            name: "agent-run-canvas",
            build: agent_run_canvas,
            shows: Shows::Subjects(&["AgentRunCanvas"]),
        },
        Scene {
            name: "agent-run-issues",
            build: agent_run_issues,
            shows: Shows::Subjects(&["AgentRunIssues"]),
        },
        Scene {
            name: "agent-avatar",
            build: agent_avatar,
            shows: Shows::Subjects(&["AgentActivityLine", "AgentAvatar"]),
        },
        Scene {
            name: "conversation",
            build: conversation,
            shows: Shows::Subjects(&["MessageList"]),
        },
        Scene {
            name: "outline",
            build: outline,
            shows: Shows::Subjects(&["Outline"]),
        },
        Scene {
            name: "conversation-growing",
            build: conversation_growing,
            shows: Shows::Subjects(&["MessageList"]),
        },
        Scene {
            name: "image-viewer",
            build: image_viewer,
            shows: Shows::Subjects(&["ImageViewer"]),
        },
        Scene {
            name: "transport",
            build: transport,
            shows: Shows::Subjects(&["TransportBar"]),
        },
        Scene {
            name: "audio-player",
            build: audio_player,
            shows: Shows::Subjects(&["AudioPlayer"]),
        },
        Scene {
            name: "audio-waveform",
            build: audio_waveform,
            shows: Shows::Subjects(&["AudioWaveform"]),
        },
        Scene {
            name: "video-player",
            build: video_player,
            shows: Shows::Subjects(&["VideoPlayer"]),
        },
        Scene {
            name: "model-viewer",
            build: model_viewer,
            shows: Shows::Subjects(&["ModelViewer"]),
        },
        Scene {
            name: "approval",
            build: approval,
            shows: Shows::Subjects(&["ApprovalPrompt"]),
        },
        Scene {
            name: "clarification",
            build: clarification,
            shows: Shows::Subjects(&["ClarificationPanel"]),
        },
        Scene {
            name: "permission-matrix",
            build: permission_matrix,
            shows: Shows::Subjects(&["PermissionMatrix"]),
        },
        Scene {
            name: "cost-meter",
            build: cost_meter,
            shows: Shows::Subjects(&["ContextGauge", "CostMeter"]),
        },
        Scene {
            name: "prompt-builder",
            build: prompt_builder,
            shows: Shows::Subjects(&["PromptBuilder"]),
        },
        Scene {
            name: "feedback-rating",
            build: feedback_rating,
            shows: Shows::Subjects(&["FeedbackRating"]),
        },
        Scene {
            name: "artifact-preview",
            build: artifact_preview,
            shows: Shows::Subjects(&["ArtifactPreview"]),
        },
        Scene {
            name: "tool-call",
            build: tool_call,
            shows: Shows::Subjects(&["ToolCall"]),
        },
        Scene {
            name: "agent-plan",
            build: agent_plan,
            shows: Shows::Subjects(&["AgentPlan"]),
        },
        Scene {
            name: "node-graph",
            build: node_graph,
            shows: Shows::Subjects(&["GraphNode", "NodeGraph"]),
        },
        Scene {
            name: "node-graph-layout",
            build: node_graph_layout,
            shows: Shows::Subjects(&["GraphNode", "NodeGraph"]),
        },
        Scene {
            name: "node-graph-routing",
            build: node_graph_routing,
            shows: Shows::Subjects(&["GraphNode", "NodeGraph"]),
        },
        Scene {
            name: "node-graph-motion",
            build: node_graph_motion,
            shows: Shows::Subjects(&["GraphNode", "NodeGraph"]),
        },
        Scene {
            name: "canvas-tools",
            build: canvas_tools,
            shows: Shows::Subjects(&["CanvasToolbar", "Minimap", "NodeGroup"]),
        },
        Scene {
            name: "canvas-regions",
            build: canvas_regions,
            shows: Shows::Subjects(&["NodeGraph"]),
        },
        Scene {
            name: "browser-panel",
            build: browser_panel,
            shows: Shows::Subjects(&["BrowserPanel"]),
        },
        Scene {
            name: "thinking",
            build: thinking,
            shows: Shows::Subjects(&["ThinkingBlock"]),
        },
        Scene {
            name: "json-view",
            build: json_view,
            shows: Shows::Subjects(&["JsonView"]),
        },
        Scene {
            name: "schema-form",
            build: schema_form,
            shows: Shows::Subjects(&["SchemaForm"]),
        },
        Scene {
            name: "server-list",
            build: server_list,
            shows: Shows::Subjects(&["ServerList"]),
        },
        Scene {
            name: "offering-catalog",
            build: offering_catalog,
            shows: Shows::Subjects(&["OfferingCatalog"]),
        },
        Scene {
            name: "reading-direction",
            build: reading_direction,
            shows: Shows::Composition(&[
                "Accordion",
                "Breadcrumb",
                "Button",
                "Card",
                "Icon",
                "JsonView",
                "Tabs",
                "Tree",
            ]),
        },
        Scene {
            name: "toggle",
            build: toggle,
            shows: Shows::Subjects(&["Toggle", "ToggleGroup"]),
        },
        Scene {
            name: "collapsible",
            build: collapsible,
            shows: Shows::Subjects(&["Collapsible"]),
        },
        Scene {
            name: "hover-card",
            build: hover_card,
            shows: Shows::Subjects(&["HoverCard"]),
        },
        Scene {
            name: "menubar",
            build: menubar,
            shows: Shows::Subjects(&["Menubar"]),
        },
        Scene {
            name: "copy-button",
            build: copy_button,
            shows: Shows::Subjects(&["CopyButton"]),
        },
        Scene {
            name: "aspect-ratio",
            build: aspect_ratio,
            shows: Shows::Subjects(&["AspectRatio"]),
        },
        Scene {
            name: "responsive",
            build: responsive,
            shows: Shows::Subjects(&["Responsive"]),
        },
        Scene {
            name: "icon",
            build: icon,
            shows: Shows::Subjects(&["Icon"]),
        },
        Scene {
            name: "document-tabs",
            build: document_tabs,
            shows: Shows::Subjects(&["Tabs"]),
        },
        Scene {
            name: "search-input",
            build: search_input,
            shows: Shows::Subjects(&["SearchInput"]),
        },
        Scene {
            name: "search-field",
            build: search_field,
            shows: Shows::Subjects(&["HighlightedText", "SearchField"]),
        },
        Scene {
            name: "find-replace",
            build: find_replace,
            shows: Shows::Subjects(&["FindReplace"]),
        },
        Scene {
            name: "notification-center",
            build: notification_center,
            shows: Shows::Subjects(&["NotificationCenter"]),
        },
        Scene {
            name: "failure-panel",
            build: failure_panel,
            shows: Shows::Subjects(&["FailurePanel"]),
        },
        Scene {
            name: "log-stream",
            build: log_stream,
            shows: Shows::Subjects(&["LogStream"]),
        },
        Scene {
            name: "diff-view",
            build: diff_view,
            shows: Shows::Subjects(&["DiffView"]),
        },
        Scene {
            name: "sparkline",
            build: sparkline,
            shows: Shows::Subjects(&["Sparkline"]),
        },
        Scene {
            name: "performance-hud",
            build: performance_hud,
            shows: Shows::Subjects(&["PerformanceHud"]),
        },
        Scene {
            name: "chart",
            build: chart,
            shows: Shows::Subjects(&[
                "AreaChart",
                "BarChart",
                "ChartLegend",
                "GaugeChart",
                "LineChart",
                "PieChart",
                "RadarChart",
                "ScatterChart",
                "StackedBarChart",
            ]),
        },
        Scene {
            name: "cartesian",
            build: cartesian,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "cartesian-linked",
            build: cartesian_linked,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "cartesian-layout",
            build: cartesian_layout,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "cartesian-lifecycle",
            build: cartesian_lifecycle,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "cartesian-dense",
            build: cartesian_dense,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "cartesian-states",
            build: cartesian_states,
            shows: Shows::Subjects(&["CartesianChart", "PieChart", "RadarChart", "GaugeChart"]),
        },
        Scene {
            name: "plot",
            build: plot,
            shows: Shows::Subjects(&["CandlestickChart", "Plot", "SankeyChart"]),
        },
        Scene {
            name: "specialized",
            build: specialized,
            shows: Shows::Subjects(&["SpecializedChart"]),
        },
        Scene {
            name: "specialized-distribution",
            build: specialized_distribution,
            shows: Shows::Subjects(&["SpecializedChart"]),
        },
        Scene {
            name: "specialized-exploration",
            build: specialized_exploration,
            shows: Shows::Subjects(&["SpecializedChart"]),
        },
        Scene {
            name: "continuous-heatmap-transition",
            build: continuous_heatmap_transition,
            shows: Shows::Subjects(&["ContinuousHeatmap"]),
        },
        Scene {
            name: "heatmap-reordering",
            build: heatmap_reordering,
            shows: Shows::Subjects(&["ContinuousHeatmap"]),
        },
        Scene {
            name: "sankey-motion",
            build: sankey_motion,
            shows: Shows::Subjects(&["SankeyChart"]),
        },
        Scene {
            name: "continuous-heatmap",
            build: continuous_heatmap,
            shows: Shows::Subjects(&["ContinuousHeatmap"]),
        },
        Scene {
            name: "sankey-layout",
            build: sankey_layout,
            shows: Shows::Subjects(&["SankeyChart"]),
        },
        Scene {
            name: "raw-candlestick",
            build: raw_candlestick,
            shows: Shows::Subjects(&["CartesianChart"]),
        },
        Scene {
            name: "geography",
            build: geography,
            shows: Shows::Subjects(&["GeoMap"]),
        },
        Scene {
            name: "geography-scale",
            build: geography_scale,
            shows: Shows::Subjects(&["GeoMap"]),
        },
        Scene {
            name: "attachment",
            build: attachment,
            shows: Shows::Subjects(&["AttachmentTile"]),
        },
        Scene {
            name: "metric-card",
            build: metric_card,
            shows: Shows::Subjects(&["MetricCard"]),
        },
        Scene {
            name: "kanban",
            build: kanban,
            shows: Shows::Subjects(&["KanbanBoard"]),
        },
        Scene {
            name: "micro",
            build: micro,
            shows: Shows::Subjects(&["MicroMark"]),
        },
        Scene {
            name: "trace",
            build: trace,
            shows: Shows::Subjects(&["SpanTimeline", "TraceView"]),
        },
        Scene {
            name: "trace-time",
            build: trace_time,
            shows: Shows::Subjects(&["SpanTimeline", "TraceView"]),
        },
        Scene {
            name: "heatmap",
            build: heatmap,
            shows: Shows::Subjects(&["Heatmap"]),
        },
        Scene {
            name: "color-picker",
            build: color_picker,
            shows: Shows::Subjects(&["ColorPicker", "ColorSwatch"]),
        },
        Scene {
            name: "code-view",
            build: code_view,
            shows: Shows::Subjects(&["CodeView"]),
        },
        Scene {
            name: "upload-list",
            build: upload_list,
            shows: Shows::Subjects(&["UploadList"]),
        },
        Scene {
            name: "cascader",
            build: cascader,
            shows: Shows::Subjects(&["Cascader"]),
        },
        Scene {
            name: "anchor-list",
            build: anchor_list,
            shows: Shows::Subjects(&["AnchorList"]),
        },
        Scene {
            name: "diagnostics-list",
            build: diagnostics_list,
            shows: Shows::Subjects(&["DiagnosticsList"]),
        },
    ];
    #[cfg(all(feature = "terminal", not(target_family = "wasm")))]
    scenes.push(Scene {
        name: "terminal",
        build: terminal,
        shows: Shows::Subjects(&["Terminal"]),
    });
    #[cfg(feature = "fixtures")]
    scenes.extend([
        Scene {
            name: "calendar",
            build: calendar,
            shows: Shows::Subjects(&["Calendar"]),
        },
        Scene {
            name: "date-range",
            build: date_range,
            shows: Shows::Subjects(&["RangePicker"]),
        },
        Scene {
            name: "date-time",
            build: date_time,
            shows: Shows::Subjects(&["DateInput", "TimeInput"]),
        },
        Scene {
            name: "touch-dates",
            build: touch_dates,
            shows: Shows::Subjects(&["DateInput", "TimeInput", "RangePicker"]),
        },
    ]);
    scenes
}

/// The scene registered under `name`, or `None` when nothing is.
pub fn find(name: &str) -> Option<Scene> {
    catalog().into_iter().find(|scene| scene.name == name)
}

/// The reading direction a scene expects.
///
/// The direction is a global, like the theme, so a capture run that renders
/// the whole catalog into one process has to set it per scene the same way it
/// activates a theme per scene. A scene that changed it while rendering would
/// leak into whichever scene came next, and the leak would show up as a
/// changed image somewhere else entirely.
pub fn direction(name: &str) -> LayoutDirection {
    match name {
        "reading-direction" => LayoutDirection::RightToLeft,
        _ => LayoutDirection::LeftToRight,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn scene_names_are_unique_and_addressable() {
        let mut names: Vec<&str> = catalog().iter().map(|scene| scene.name).collect();
        let count = names.len();
        names.sort_unstable();
        names.dedup();
        assert_eq!(names.len(), count);
        assert!(find("button").is_some());
        assert!(find("nothing").is_none());
    }
}

//! Motion primitives exercised against a live clock, plus described motion at rest.

use super::support::*;
use crate::motion::{
    CubicBezier, Easing, Keyframe, Keyframes, MotionSpec, Presence, Spring, Stagger, Transition,
};
use web_time::Instant;

const CLOCK_START: u16 = 8 * 60;
const CLOCK_END: u16 = 20 * 60;
const CLOCK_STEP: u16 = 30;
const CLOCK_TICK: Duration = Duration::from_millis(500);
const CLOCK_TRANSITION_MS: u64 = 620;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MotionDemo {
    SlidingTime,
    Spring,
    Keyframes,
    Presence,
    Stagger,
}

impl MotionDemo {
    fn id(self) -> &'static str {
        match self {
            Self::SlidingTime => "sliding-time",
            Self::Spring => "spring",
            Self::Keyframes => "keyframes",
            Self::Presence => "presence",
            Self::Stagger => "stagger",
        }
    }

    fn from_id(id: &str) -> Option<Self> {
        match id {
            "sliding-time" => Some(Self::SlidingTime),
            "spring" => Some(Self::Spring),
            "keyframes" => Some(Self::Keyframes),
            "presence" => Some(Self::Presence),
            "stagger" => Some(Self::Stagger),
            _ => None,
        }
    }
}

/// Caller-owned state for the five demonstrations.
///
/// The catalog rebuilds a scene every frame, just as an ordinary host rebuilds
/// component builders. The state therefore lives outside the element tree;
/// every animated channel beneath it still has a stable semantic identity.
#[derive(Debug)]
struct SceneMotionPrimitives {
    demo: MotionDemo,
    clock_minutes: u16,
    clock_targets: [f32; 4],
    clock_digits: [Transition<f32>; 4],
    clock_playing: bool,
    clock_started_at: Option<Instant>,
    clock_ticks: u16,
    spring_selected: usize,
    spring_indicator: Transition<f32>,
    present: bool,
    presence: Presence,
    keyframes_started_at: Option<Instant>,
    stagger_generation: u64,
    stagger_started_at: Option<Instant>,
}

impl Global for SceneMotionPrimitives {}

impl SceneMotionPrimitives {
    fn new(theme: &Theme) -> Self {
        let clock_spec = clock_spec(theme);
        let spring_spec = spring_spec();
        let presence_spec = presence_spec(theme);
        let targets = clock_digits(CLOCK_START).map(f32::from);
        Self {
            demo: MotionDemo::SlidingTime,
            clock_minutes: CLOCK_START,
            clock_targets: targets,
            clock_digits: targets.map(|digit| Transition::new(digit, clock_spec)),
            clock_playing: false,
            clock_started_at: None,
            clock_ticks: 0,
            spring_selected: 0,
            spring_indicator: Transition::new(0.0, spring_spec),
            present: true,
            presence: Presence::visible(presence_spec, presence_spec),
            keyframes_started_at: None,
            stagger_generation: 0,
            stagger_started_at: None,
        }
    }

    fn select(&mut self, demo: MotionDemo, theme: &Theme) {
        self.demo = demo;
        // Element-retained animation state disappears when its tab leaves the
        // tree. Recreate that boundary here even though the scene state itself
        // is longer lived, so returning to a tab never resumes an invisible
        // half-frame from the previous visit.
        match demo {
            MotionDemo::SlidingTime => {
                for (digit, target) in self
                    .clock_digits
                    .iter_mut()
                    .zip(self.clock_targets.iter().copied())
                {
                    *digit = Transition::new(target, clock_spec(theme));
                }
            }
            MotionDemo::Spring => self.spring_indicator.snap(if self.spring_selected == 0 {
                0.0
            } else {
                120.0
            }),
            MotionDemo::Keyframes => self.keyframes_started_at = None,
            MotionDemo::Presence => {
                let spec = presence_spec(theme);
                self.presence = if self.present {
                    Presence::visible(spec, spec)
                } else {
                    Presence::hidden(spec, spec)
                };
            }
            MotionDemo::Stagger => self.stagger_started_at = None,
        }
    }

    fn toggle_clock(&mut self, now: Instant, theme: &Theme) {
        if self.clock_playing {
            self.clock_playing = false;
            self.clock_started_at = None;
            self.clock_ticks = 0;
            return;
        }
        if self.clock_minutes >= CLOCK_END {
            self.clock_minutes = CLOCK_START;
            self.clock_targets = clock_digits(CLOCK_START).map(f32::from);
            for (digit, target) in self
                .clock_digits
                .iter_mut()
                .zip(self.clock_targets.iter().copied())
            {
                *digit = Transition::new(target, clock_spec(theme));
            }
        }
        self.clock_playing = true;
        self.clock_started_at = Some(now);
        self.clock_ticks = 0;
    }

    fn update_clock(&mut self, now: Instant) {
        let Some(started) = self.clock_started_at else {
            return;
        };
        let due = now
            .saturating_duration_since(started)
            .as_millis()
            .checked_div(CLOCK_TICK.as_millis())
            .unwrap_or(0) as u16;
        while self.clock_ticks < due && self.clock_minutes < CLOCK_END {
            self.clock_ticks += 1;
            self.clock_minutes = (self.clock_minutes + CLOCK_STEP).min(CLOCK_END);
            for (target, next) in self
                .clock_targets
                .iter_mut()
                .zip(clock_digits(self.clock_minutes))
            {
                *target = advance_digit(*target, next);
            }
        }
        if self.clock_minutes >= CLOCK_END {
            self.clock_playing = false;
            self.clock_started_at = None;
            self.clock_ticks = 0;
        }
    }

    fn sliding_time(&mut self, theme: &Theme, window: &mut Window, cx: &mut App) -> AnyElement {
        let spec = clock_spec(theme);
        let values: [f32; 4] = std::array::from_fn(|index| {
            let mut digit = self.clock_digits[index].spec(spec);
            digit.set(self.clock_targets[index]);
            let value = digit.animate(window, cx);
            self.clock_digits[index] = digit;
            value
        });
        let announced = format_time(self.clock_minutes);
        let play_label = if self.clock_playing { "Pause" } else { "Play" };

        demo_panel(theme, "Four interrupted transitions on one clock")
            .child(
                div()
                    .row()
                    .items_center()
                    .gap(px(theme.space(Space::Xs)))
                    .child(rolling_digit(theme, values[0]))
                    .child(rolling_digit(theme, values[1]))
                    .child(
                        div()
                            .h(px(52.0))
                            .flex()
                            .items_center()
                            .mono(theme)
                            .text_size(px(36.0))
                            .child(":"),
                    )
                    .child(rolling_digit(theme, values[2]))
                    .child(rolling_digit(theme, values[3]))
                    .semantic_in(
                        cx,
                        NodeSpec::new("scene.motion.clock", Role::Status)
                            .text("Simulated time")
                            .value(announced),
                    ),
            )
            .child(
                Button::new("scene.motion.clock.play")
                    .label(play_label)
                    .secondary()
                    .on_click(|_, cx| {
                        let now = cx.background_executor().now();
                        let theme = cx.theme().clone();
                        cx.update_global::<SceneMotionPrimitives, ()>(|scene, _| {
                            scene.toggle_clock(now, &theme)
                        });
                        cx.refresh_windows();
                    }),
            )
            .into_any_element()
    }

    fn spring(&mut self, theme: &Theme, window: &mut Window, cx: &mut App) -> AnyElement {
        let mut indicator = self.spring_indicator.spec(spring_spec());
        indicator.set(if self.spring_selected == 0 {
            0.0
        } else {
            120.0
        });
        let left = indicator.animate(window, cx);
        self.spring_indicator = indicator;
        let selected = self.spring_selected;

        let segment = |id: &'static str, label: &'static str, index: usize| {
            div().w(px(120.0)).child(
                Button::new(id)
                    .label(label)
                    .ghost()
                    .selected(selected == index)
                    .full_width(true)
                    .on_click(move |_, cx| {
                        cx.update_global::<SceneMotionPrimitives, ()>(|scene, _| {
                            scene.spring_selected = index
                        });
                        cx.refresh_windows();
                    }),
            )
        };

        demo_panel(theme, "Velocity survives rapid retargeting")
            .child(
                div()
                    .relative()
                    .w(px(240.0))
                    .radius(theme, Radius::Control)
                    .well(theme)
                    .child(
                        div()
                            .row()
                            .child(segment("scene.motion.spring.queue", "Queue", 0))
                            .child(segment("scene.motion.spring.timeline", "Timeline", 1)),
                    )
                    .child(
                        div()
                            .absolute()
                            .left(px(left))
                            .top_0()
                            .w(px(120.0))
                            .h(px(theme.control.md.height))
                            .radius(theme, Radius::Control)
                            .bg(theme.colors.selected)
                            .semantic_in(
                                cx,
                                NodeSpec::new("scene.motion.spring.indicator", Role::Status)
                                    .text("Selected segment indicator")
                                    .value(if selected == 0 { "Queue" } else { "Timeline" }),
                            ),
                    ),
            )
            .into_any_element()
    }

    fn keyframes(&mut self, theme: &Theme, window: &mut Window, cx: &mut App) -> AnyElement {
        let now = cx.background_executor().now();
        let started = *self.keyframes_started_at.get_or_insert(now);
        let elapsed = now.saturating_duration_since(started);
        let period = Duration::from_millis(1_200);
        let still = cx.reduce_motion();
        if !still {
            window.request_animation_frame();
        }
        let path = activity_keyframes(theme);
        let bars = [
            "clone", "resolve", "compile", "test", "lint", "package", "publish",
        ];

        demo_panel(theme, "One validated path, seven delayed playheads")
            .child(
                div()
                    .h(px(72.0))
                    .row()
                    .items_end()
                    .gap(px(theme.space(Space::Sm)))
                    .children(bars.into_iter().enumerate().map(|(index, name)| {
                        let delay = Duration::from_millis(index as u64 * 80);
                        let phase = if still || elapsed < delay {
                            0.0
                        } else {
                            elapsed.saturating_sub(delay).as_secs_f32() / period.as_secs_f32()
                        };
                        let value = path.sample(phase.rem_euclid(1.0));
                        div()
                            .w(px(18.0))
                            .h(px(18.0 + 38.0 * value))
                            .radius(theme, Radius::Small)
                            .bg(theme.colors.accent)
                            .opacity(0.35 + 0.65 * value)
                            .semantic_in(
                                cx,
                                NodeSpec::new(
                                    format!("scene.motion.keyframes.{name}"),
                                    Role::Status,
                                )
                                .text(format!("{name} activity"))
                                .value(format!("{value:.3}")),
                            )
                    })),
            )
            .into_any_element()
    }

    fn presence(&mut self, theme: &Theme, window: &mut Window, cx: &mut App) -> AnyElement {
        let progress = self.presence.animate(window, cx);
        let visible = self.presence.is_rendered();
        let button = if self.present {
            "Hide notice"
        } else {
            "Show notice"
        };

        demo_panel(theme, "Logical absence waits for the exit to finish")
            .child(
                div()
                    .h(px(76.0))
                    .flex()
                    .items_center()
                    .children(visible.then(|| {
                        div()
                            .w(px(360.0))
                            .p(px(theme.space(Space::Md)))
                            .radius(theme, Radius::Card)
                            .bg(theme.colors.accent.opacity(0.14))
                            .opacity(progress)
                            .child(crate::foundation::text(
                                theme,
                                TypeScale::Body,
                                "The verified result is ready to review.",
                            ))
                            .semantic_in(
                                cx,
                                NodeSpec::new("scene.motion.presence.notice", Role::Status)
                                    .text("The verified result is ready to review."),
                            )
                    })),
            )
            .child(
                Button::new("scene.motion.presence.toggle")
                    .label(button)
                    .secondary()
                    .on_click(|_, cx| {
                        cx.update_global::<SceneMotionPrimitives, ()>(|scene, _| {
                            scene.present = !scene.present;
                            if scene.present {
                                scene.presence.show();
                            } else {
                                scene.presence.hide();
                            }
                        });
                        cx.refresh_windows();
                    }),
            )
            .into_any_element()
    }

    fn stagger(&mut self, theme: &Theme, window: &mut Window, cx: &mut App) -> AnyElement {
        let now = cx.background_executor().now();
        let started = *self.stagger_started_at.get_or_insert(now);
        let elapsed = now.saturating_duration_since(started);
        let spec = MotionSpec::new(360, Easing::EaseOut.curve(theme));
        let stagger = Stagger::from_millis(90);
        let total = stagger.total(3, spec);
        let still = cx.reduce_motion();
        if !still && elapsed < total {
            window.request_animation_frame();
        }
        let rows = [
            ("plan", "Plan the release"),
            ("build", "Build the artifacts"),
            ("publish", "Publish the result"),
        ];

        demo_panel(theme, "A shared clock spreads one entrance across a list")
            .child(div().column().gap(px(theme.space(Space::Sm))).children(
                rows.into_iter().enumerate().map(|(index, (id, label))| {
                    let progress = if still {
                        1.0
                    } else {
                        stagger.progress_at(elapsed, index, 3, spec)
                    };
                    div()
                        .relative()
                        .left(px(24.0 * (1.0 - progress)))
                        .w(px(360.0))
                        .p(px(theme.space(Space::Sm)))
                        .radius(theme, Radius::Control)
                        .well(theme)
                        .opacity(progress)
                        .child(crate::foundation::text(theme, TypeScale::Body, label))
                        .semantic_in(
                            cx,
                            NodeSpec::new(format!("scene.motion.stagger.{id}"), Role::Status)
                                .text(label)
                                .value(format!("replay {}", self.stagger_generation)),
                        )
                }),
            ))
            .child(
                Button::new("scene.motion.stagger.replay")
                    .label("Replay")
                    .secondary()
                    .on_click(|_, cx| {
                        let now = cx.background_executor().now();
                        cx.update_global::<SceneMotionPrimitives, ()>(|scene, _| {
                            scene.stagger_generation += 1;
                            scene.stagger_started_at = Some(now);
                        });
                        cx.refresh_windows();
                    }),
            )
            .into_any_element()
    }
}

/// Five interactive demonstrations of interruption, physical motion,
/// authored timelines, delayed unmount and list choreography.
pub(super) fn motion_primitives(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneMotionPrimitives>() {
        cx.set_global(SceneMotionPrimitives::new(cx.theme()));
    }
    let now = cx.background_executor().now();
    let theme = cx.theme().clone();
    let selected = cx.global::<SceneMotionPrimitives>().demo;
    let tabs = Tabs::new("scene.motion.tabs")
        .tabs([
            TabItem::new("sliding-time", "Sliding time"),
            TabItem::new("spring", "Spring"),
            TabItem::new("keyframes", "Keyframes"),
            TabItem::new("presence", "Presence"),
            TabItem::new("stagger", "Stagger"),
        ])
        .selected(selected.id())
        .on_select(|id, _, cx| {
            let Some(demo) = MotionDemo::from_id(id.as_ref()) else {
                return;
            };
            let theme = cx.theme().clone();
            cx.update_global::<SceneMotionPrimitives, ()>(|scene, _| scene.select(demo, &theme));
            cx.refresh_windows();
        });

    let body = cx.update_global::<SceneMotionPrimitives, AnyElement>(|scene, cx| {
        scene.update_clock(now);
        if scene.clock_playing {
            window.request_animation_frame();
        }
        match scene.demo {
            MotionDemo::SlidingTime => scene.sliding_time(&theme, window, cx),
            MotionDemo::Spring => scene.spring(&theme, window, cx),
            MotionDemo::Keyframes => scene.keyframes(&theme, window, cx),
            MotionDemo::Presence => scene.presence(&theme, window, cx),
            MotionDemo::Stagger => scene.stagger(&theme, window, cx),
        }
    });

    stack(&theme)
        .w(px(640.0))
        .child(tabs)
        .child(body)
        .into_any_element()
}

fn clock_spec(theme: &Theme) -> MotionSpec {
    MotionSpec::new(CLOCK_TRANSITION_MS, Easing::EaseInOut.curve(theme))
}

fn spring_spec() -> MotionSpec {
    // `bounce = 1 - damping_ratio`, so this is the same 420ms / 0.68 policy
    // used by the reference segmented indicator.
    MotionSpec::sprung(Spring::perceptual(Duration::from_millis(420), 0.32))
}

fn presence_spec(theme: &Theme) -> MotionSpec {
    MotionSpec::new(360, Easing::EaseInOut.curve(theme))
}

fn activity_keyframes(theme: &Theme) -> Keyframes<f32> {
    let linear = MotionSpec::new(1_200, CubicBezier::new(0.0, 0.0, 1.0, 1.0));
    Keyframes::new(
        theme,
        linear,
        [
            Keyframe::new(0.0, 0.0),
            Keyframe::new(0.35, 1.0),
            // Local keyframes attach the curve to the stop being reached. Put
            // EaseOut on the 0.7 stop so the outgoing 0.35..0.7 segment has
            // the same curve as the reference implementation.
            Keyframe::new(0.7, 0.0).eased(Easing::EaseOut),
            Keyframe::new(1.0, 0.0),
        ],
    )
    .expect("the activity path has four stops")
}

fn demo_panel(theme: &Theme, caption_text: &'static str) -> gpui::Div {
    div()
        .min_h(px(260.0))
        .w_full()
        .column()
        .items_center()
        .justify_center()
        .gap(px(theme.space(Space::Lg)))
        .p(px(theme.space(Space::Xl)))
        .radius(theme, Radius::Card)
        .card_surface(theme, CardVariant::Elevated)
        .child(caption(theme, caption_text))
}

fn rolling_digit(theme: &Theme, value: f32) -> gpui::Div {
    let whole = value.floor();
    let fraction = value - whole;
    let current = (whole as i32).rem_euclid(10);
    let next = (current + 1).rem_euclid(10);
    let glyph = |digit: i32, top: f32| {
        div()
            .absolute()
            .left_0()
            .right_0()
            .top(px(top))
            .h(px(52.0))
            .flex()
            .items_center()
            .justify_center()
            .child(digit.to_string())
    };

    div()
        .relative()
        .w(px(38.0))
        .h(px(52.0))
        .overflow_hidden()
        .radius(theme, Radius::Control)
        .well(theme)
        .mono(theme)
        .text_size(px(40.0))
        .child(glyph(current, -52.0 * fraction))
        .child(glyph(next, 52.0 * (1.0 - fraction)))
}

fn clock_digits(minutes: u16) -> [u8; 4] {
    let hour = minutes / 60;
    let minute = minutes % 60;
    [
        (hour / 10) as u8,
        (hour % 10) as u8,
        (minute / 10) as u8,
        (minute % 10) as u8,
    ]
}

fn format_time(minutes: u16) -> String {
    format!("{:02}:{:02}", minutes / 60, minutes % 60)
}

/// The next scalar congruent to `digit`, always travelling forward around the
/// decimal wheel. Thus 8 → 0 becomes 8 → 10 rather than rolling backwards.
fn advance_digit(current: f32, digit: u8) -> f32 {
    let current_digit = (current.round() as i32).rem_euclid(10);
    let distance = (i32::from(digit) - current_digit).rem_euclid(10);
    current + distance as f32
}

pub(super) fn micro(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    // Each motion gets a cell of its own. A still frame cannot show any of
    // them moving, so what the picture has to carry instead is that there are
    // five of them, that they are the same size, and which name belongs to
    // which mark.
    let cell = |mark: MicroMark| {
        div()
            .w(px(96.0))
            .flex_none()
            .column()
            .items_center()
            .justify_center()
            .gap_token(&theme, Space::Xs)
            .py_token(&theme, Space::Md)
            .radius(&theme, Radius::Card)
            .well(&theme)
            .child(mark)
    };

    stack(&theme)
        .w(px(560.0))
        .child(caption(
            &theme,
            "named functions of time; reduced motion leaves the glyph still",
        ))
        .child(
            row(&theme)
                .gap_token(&theme, Space::Sm)
                .child(cell(MicroMark::new(
                    "scene.micro.heartbeat",
                    Micro::Heartbeat,
                    "Hb",
                )))
                .child(cell(MicroMark::new(
                    "scene.micro.bounce",
                    Micro::Bounce,
                    "Bn",
                )))
                .child(cell(MicroMark::new(
                    "scene.micro.wobble",
                    Micro::Wobble,
                    "Wb",
                )))
                .child(cell(MicroMark::new("scene.micro.pop", Micro::Pop, "Pp")))
                .child(cell(MicroMark::new(
                    "scene.micro.sparkle",
                    Micro::Sparkle,
                    "Sk",
                ))),
        )
        .child(caption(
            &theme,
            "the same functions on things that are not glyphs",
        ))
        .child(
            row(&theme)
                .gap_token(&theme, Space::Sm)
                .child(div().child(Badge::new("12 unread").neutral()).micro(
                    "scene.micro.badge",
                    Micro::Pop,
                    cx,
                ))
                .child(div().child(Tag::new("scene.micro.tag", "fixture")).micro(
                    "scene.micro.tag.mark",
                    Micro::Wobble,
                    cx,
                ))
                .child(div().child(StatusDot::new(Tone::Success)).micro(
                    "scene.micro.dot",
                    Micro::Heartbeat,
                    cx,
                )),
        )
        .child(caption(
            &theme,
            "a described motion, sampled along its own run rather than played",
        ))
        .child(described(&theme))
        .into_any_element()
}

/// A motion written with `motion!`, laid out as the frames it passes through.
///
/// This is the one thing a still frame can say about motion: a described
/// motion can be read at a point in its run without running it, so the frames
/// are drawn side by side and the picture is the same every time. Playing it
/// is [`Animator`]'s job and needs a window; the gallery is where that is
/// looked at.
fn described(theme: &Theme) -> gpui::Div {
    let arrive = crate::motion! {
        duration: 420;
        ease: overshoot;
        opacity: 0.0 => 1.0;
        y: 12.0 => 0.0;
    };

    let frame = |head: f32, label: &'static str| {
        let sample = arrive.sample(theme, head);
        div()
            .w(px(96.0))
            .flex_none()
            .column()
            .items_center()
            .gap_token(theme, Space::Xs)
            .py_token(theme, Space::Md)
            .radius(theme, Radius::Card)
            .well(theme)
            // The slot stays drawn at every head. At the start of the run the
            // mark has not arrived yet, and a cell with nothing in it reads
            // as a frame that failed to render rather than as the frame where
            // the motion has not begun.
            .child(
                div()
                    .w(px(56.0))
                    .h(px(24.0))
                    .radius(theme, Radius::Control)
                    .bg(theme.colors.track)
                    .child(
                        sample.apply(
                            div()
                                .size_full()
                                .radius(theme, Radius::Control)
                                .bg(theme.colors.accent),
                        ),
                    ),
            )
            .child(caption(theme, label))
    };

    row(theme)
        .gap_token(theme, Space::Sm)
        .child(frame(0.0, "start"))
        .child(frame(0.25, "quarter"))
        .child(frame(0.5, "half"))
        .child(frame(0.75, "three quarters"))
        .child(frame(1.0, "settled"))
}

//! Getting to a place, and knowing which place you are in.

use super::support::*;

pub(super) fn bottom_navigation(_window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::navigation::{BottomNavigation, NavigationItem};
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(390.0))
        .child(caption(
            &theme,
            "Fixture destinations; selected, enabled and disabled actions",
        ))
        .child(
            BottomNavigation::new("scene.bottom-navigation.ready")
                .items([
                    NavigationItem::new("library", "Library"),
                    NavigationItem::new("search", "Search"),
                    NavigationItem::new("settings", "Settings").disabled(true),
                ])
                .selected("search")
                .on_select(|_, _, _| {}),
        )
        .child(caption(
            &theme,
            "No host handler: destinations are unavailable",
        ))
        .child(
            BottomNavigation::new("scene.bottom-navigation.unavailable")
                .items([
                    NavigationItem::new("library", "Library"),
                    NavigationItem::new("search", "Search"),
                ])
                .selected("library"),
        )
        .into_any_element()
}

pub(super) fn adaptive_navigation(_window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::layout::{AppBar, PageLayout, Responsive};
    use crate::navigation::{BottomNavigation, NavigationItem};
    let theme = cx.theme().clone();
    let make = |id: &'static str, width: f32| {
        div().w(px(width)).h(px(320.0)).child(
            Responsive::new(id, move |size, _, _| {
                let wide = size.at_least(600.0);
                let body = div()
                    .flex()
                    .size_full()
                    .when(wide, |body| {
                        body.child(
                            Sidebar::new(format!("{id}.rail"))
                                .section(SidebarSection::new("destinations").items([
                                    SidebarItem::new("library", "Library"),
                                    SidebarItem::new("search", "Search"),
                                ]))
                                .active("search")
                                .on_select(|_, _, _| {}),
                        )
                    })
                    .child(
                        div()
                            .flex_1()
                            .p(px(16.0))
                            .child("Fixture content · caller owns route selection"),
                    );
                PageLayout::new(format!("{id}.page"), body)
                    .insets(gpui::WindowInsets::default())
                    .header(AppBar::new(
                        format!("{id}.bar"),
                        if wide {
                            "Wide container"
                        } else {
                            "Compact container"
                        },
                    ))
                    .hide_footer(wide)
                    .footer(
                        BottomNavigation::new(format!("{id}.bottom"))
                            .items([
                                NavigationItem::new("library", "Library"),
                                NavigationItem::new("search", "Search"),
                            ])
                            .selected("search")
                            .on_select(|_, _, _| {}),
                    )
                    .into_any_element()
            })
            .fill(),
        )
    };
    stack(&theme)
        .child(caption(&theme, "Same caller data; measured container width selects rail or bottom destinations. No platform guessing."))
        .child(make("scene.adaptive-navigation.wide", 640.0))
        .child(make("scene.adaptive-navigation.compact", 350.0))
        .into_any_element()
}

pub(super) fn nav_back_preview(window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::navigation::{BackTransition, NavHistory, NavStack};
    let theme = cx.theme().clone();
    let state = window.use_keyed_state("scene.nav-back-preview.state", cx, |_, cx| {
        let mut history = NavHistory::new("overview");
        history.push("details");
        (history, None::<BackTransition>, cx.focus_handle())
    });
    let (history, preview, focus) = state.read(cx).clone();
    let progress = state.clone();
    let cancel = state.clone();
    let commit = state.clone();
    let reset = state.clone();
    let mut page = NavStack::new(
        "scene.nav-back-preview",
        &history,
        history.current().clone(),
        focus,
        Card::new()
            .padding(Space::Xl)
            .child(history.current().clone()),
    );
    if let Some(preview) = &preview {
        page = page.back_transition(preview);
    }
    stack(&theme).w(px(550.0))
        .child(caption(&theme, "Fixture host-fed progress; cancel retains the page, accept mutates caller history. Reduced motion holds full opacity."))
        .child(Slider::new("scene.nav-back-preview.progress").label("Back progress")
            .range(0.0, 1.0).value(preview.as_ref().map_or(0.0, BackTransition::progress))
            .disabled(!history.can_pop()).on_change(move |value, _, cx| progress.update(cx, |state, cx| {
                if state.1.is_none() { state.1 = BackTransition::begin(&state.0); }
                if let Some(preview) = &mut state.1 { preview.update(value); }
                cx.notify();
            })))
        .child(row(&theme)
            .child(Button::new("scene.nav-back-preview.cancel").label("Cancel").disabled(preview.is_none())
                .on_click(move |_, cx| cancel.update(cx, |state, cx| {
                    if let Some(preview) = state.1.take() { preview.cancel(); } cx.notify();
                })))
            .child(Button::new("scene.nav-back-preview.accept").label("Accept back").disabled(preview.is_none())
                .on_click(move |_, cx| commit.update(cx, |state, cx| {
                    if let Some(preview) = state.1.take() { preview.commit(&mut state.0); } cx.notify();
                })))
            .child(Button::new("scene.nav-back-preview.reset").label("Forward").disabled(!history.can_forward())
                .on_click(move |_, cx| reset.update(cx, |state, cx| { state.0.forward(); cx.notify(); }))))
        .child(page).into_any_element()
}

pub(super) fn nav_stack(window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::navigation::nav_stack::{NavHistory, NavStack};
    let theme = cx.theme().clone();
    let state = window.use_keyed_state("scene.nav-stack.state", cx, |_, cx| {
        (
            NavHistory::new("overview"),
            cx.focus_handle(),
            cx.focus_handle(),
            cx.focus_handle(),
        )
    });
    let (history, overview, details, replacement) = state.read(cx).clone();
    let focus = match history.current().as_ref() {
        "details" => details,
        "replacement" => replacement,
        _ => overview,
    };
    let back = state.clone();
    let forward = state.clone();
    let push = state.clone();
    let replace = state.clone();
    stack(&theme).w(px(620.0))
        .child(caption(&theme, "Caller-owned visit history; back retains forward visits, push forks, replace preserves branches"))
        .child(row(&theme)
            .child(Button::new("scene.nav-stack.back").label("Back").disabled(!history.can_pop())
                .on_click(move |_, cx| back.update(cx, |state, cx| { state.0.pop(); cx.notify(); })))
            .child(Button::new("scene.nav-stack.forward").label("Forward").disabled(!history.can_forward())
                .on_click(move |_, cx| forward.update(cx, |state, cx| { state.0.forward(); cx.notify(); })))
            .child(Button::new("scene.nav-stack.push").label("Push details")
                .disabled(history.entries().iter().any(|id| id.as_ref() == "details"))
                .on_click(move |_, cx| push.update(cx, |state, cx| { state.0.push("details"); cx.notify(); })))
            .child(Button::new("scene.nav-stack.replace").label("Replace")
                .disabled(history.entries().iter().any(|id| id.as_ref() == "replacement"))
                .on_click(move |_, cx| replace.update(cx, |state, cx| { state.0.replace("replacement"); cx.notify(); }))))
        .child(NavStack::new("scene.nav-stack", &history, history.current().clone(), focus,
            Card::new().padding(Space::Xl)
                .child(crate::foundation::text(&theme, TypeScale::Title, history.current().clone()))
                .child(crate::foundation::text(&theme, TypeScale::Body,
                    "Only the active page is mounted. Return visits restore focus without keeping inactive controls live."))))
        .into_any_element()
}

pub(super) fn carousel(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let page = |id: &'static str, label: &'static str, text: &'static str| {
        CarouselItem::new(
            id,
            label,
            Card::new()
                .id(format!("scene.carousel.card.{id}"))
                .padding(Space::Xl)
                .child(crate::foundation::text(&theme, TypeScale::Title, label))
                .child(crate::foundation::text(&theme, TypeScale::Body, text)),
        )
    };
    stack(&theme)
        .w(px(620.0))
        .child(caption(
            &theme,
            "A controlled, focusable page track with direct selection and reduced-motion-aware transitions",
        ))
        .child(
            Carousel::new("scene.carousel.ready")
                .items([
                    page("overview", "Overview", "The first page is active."),
                    page("details", "Details", "The caller owns the page content."),
                    page("history", "History", "The track reports page intents."),
                ])
                .active("details")
                .looped(true)
                .on_event(|_, _, _| {}),
        )
        .child(
            Carousel::new("scene.carousel.empty")
                .phase(Phase::Empty)
                .on_event(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn tabs(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(caption(
            &theme,
            "In-content tabs: raised control, quiet track, tabular count",
        ))
        .child(
            Tabs::new("scene.tabs.workspace")
                .tabs([
                    TabItem::new("overview", "Overview").icon(Icon::Widget),
                    TabItem::new("runs", "Runs").badge("12"),
                    TabItem::new("logs", "Logs"),
                    TabItem::new("billing", "Billing").disabled(true),
                ])
                .selected("runs")
                .on_select(|_, _, _| {}),
        )
        .child(caption(
            &theme,
            "places, not documents: Regular Liquid capsules, \
             with a translucent selected fill on the material",
        ))
        .child(
            Tabs::new("scene.tabs.capsules")
                .capsules()
                .control_size(ControlSize::Sm)
                .tabs([
                    TabItem::new("code", "Portal")
                        .icon(Icon::Terminal)
                        .tint(theme.colors.accent),
                    TabItem::new("game", "Harbour")
                        .icon(Icon::Play)
                        .tint(theme.colors.warning),
                    TabItem::new("design", "notes")
                        .icon(Icon::Pen)
                        .tint(theme.colors.info),
                ])
                .selected("code")
                .on_select(|_, _, _| {}),
        )
        // The body belongs to the caller: tabs render the strip only.
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "Runs are rendered by the caller, not by the strip.",
        ))
        .child(caption(
            &theme,
            "the other answer to a strip with no room: it scrolls, and the \
             edge with more behind it fades",
        ))
        // Deliberately narrower than its tabs, because a strip that fits shows
        // nothing about what a strip that does not fit does.
        .child(
            div().w(px(300.0)).child(
                Tabs::new("scene.tabs.spaces")
                    .tabs((1..=9).map(|n| {
                        TabItem::new(format!("space-{n}"), format!("Workspace {n}"))
                            .icon(Icon::Widget)
                    }))
                    .selected("space-5")
                    .scrolling()
                    .on_select(|_, _, _| {}),
            ),
        )
        .into_any_element()
}

/// The overflow menu the document-tab scene hangs off the strip.
///
/// The strip does not own it: a menu is an entity with an open state that
/// outlives a frame, so the caller builds it and the strip fills it.
pub(super) struct SceneDocumentTabs {
    overflow: Entity<Menu>,
}

impl Global for SceneDocumentTabs {}

pub(super) fn document_tabs(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneDocumentTabs>() {
        // An icon-only trigger still has to be nameable, so it carries the
        // strip's own word for what moved into it.
        let name = cx.strings().text(StringKey::TabMoreTabs);
        let overflow = cx.new(|cx| {
            Menu::new("scene.document-tabs.overflow", window, cx)
                .trigger_icon(Icon::AltArrowDown)
                .trigger_name(name)
        });
        cx.set_global(SceneDocumentTabs { overflow });
    }
    let overflow = cx.global::<SceneDocumentTabs>().overflow.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(620.0))
        .child(caption(
            &theme,
            "clean, unsaved, saving, save failed — three marks, and silence for the fourth",
        ))
        .child(
            Tabs::new("scene.document-tabs.editor")
                .tabs([
                    TabItem::new("readme", "README.md").closable(true),
                    TabItem::new("main", "main.rs").dirty().closable(true),
                    TabItem::new("theme", "theme.json").saving().closable(true),
                    TabItem::new("notes", "notes.md")
                        .save_failed("The workspace is read-only.")
                        .closable(true),
                ])
                .selected("main")
                .on_select(|_, _, _| {})
                .on_close(|_, _, _| {}),
        )
        .child(caption(
            &theme,
            "past the declared limit the rest go to a menu, which stays reachable from the keyboard",
        ))
        .child(
            Tabs::new("scene.document-tabs.overflowing")
                .tabs([
                    TabItem::new("one", "adapter.rs").closable(true),
                    TabItem::new("two", "catalog.rs").dirty().closable(true),
                    TabItem::new("three", "harness.rs").closable(true),
                    TabItem::new("four", "registry.rs").closable(true),
                    TabItem::new("five", "transport.rs").dirty().closable(true),
                ])
                .selected("two")
                .overflow_after(3)
                .overflow_menu(overflow)
                .on_select(|_, _, _| {})
                .on_close(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) struct SceneAnchorList {
    overflow: Entity<Menu>,
}

impl Global for SceneAnchorList {}

pub(super) fn anchor_list(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneAnchorList>() {
        let label = cx.strings().text(StringKey::AnchorMoreSections);
        let overflow = cx.new(|cx| Menu::new("scene.anchor-list.menu", window, cx).trigger(label));
        cx.set_global(SceneAnchorList { overflow });
    }
    let theme = cx.theme().clone();
    let overflow = cx.global::<SceneAnchorList>().overflow.clone();
    stack(&theme)
        .w(px(700.0))
        .child(caption(
            &theme,
            "section intents only; the declared overflow moves anchors into a menu",
        ))
        .child(
            AnchorList::new("scene.anchor-list")
                .anchors([
                    Anchor::new("summary", "Summary"),
                    Anchor::new("inputs", "Inputs"),
                    Anchor::new("constraints", "Constraints"),
                    Anchor::new("verification", "Verification"),
                    Anchor::new("history", "History").disabled(true),
                ])
                .active("inputs")
                .overflow_after(3)
                .overflow_menu(overflow)
                .on_navigate(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn accordion(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(
            Accordion::new("scene.accordion.settings")
                .expanded_ids(&["network"])
                .on_toggle(|_, _, _, _| {})
                .section(
                    AccordionSection::new("network", "Network")
                        .description("How this machine reaches a host")
                        .body(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Requests go out over the system proxy.",
                        )),
                )
                .section(
                    AccordionSection::new("storage", "Storage")
                        .description("Where verified results are kept")
                        .body(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Nothing is written outside the workspace.",
                        )),
                )
                .section(
                    AccordionSection::new("policy", "Managed by policy")
                        .description("This machine cannot change these")
                        .disabled(true)
                        .body(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Set by the administrator.",
                        )),
                ),
        )
        // The open section is the last one, which is the case where the body
        // meets the card's rounded corners.
        .child(
            Accordion::new("scene.accordion.release")
                .expanded_ids(&["publish"])
                .on_toggle(|_, _, _, _| {})
                .section(
                    AccordionSection::new("build", "Build")
                        .description("What the last run produced"),
                )
                .section(
                    AccordionSection::new("publish", "Publish")
                        .description("Where the signed bundle goes")
                        .body(crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "The bundle is published to the workspace channel.",
                        )),
                ),
        )
        .into_any_element()
}

pub(super) fn breadcrumb(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(
            Breadcrumb::new("scene.breadcrumb.short")
                .crumbs([
                    Crumb::new("workspace", "Workspace"),
                    Crumb::new("runs", "Runs"),
                    Crumb::new("run-4821", "Indexing"),
                ])
                .on_select(|_, _, _| {}),
        )
        .child(
            Breadcrumb::new("scene.breadcrumb.long")
                .crumbs([
                    Crumb::new("workspace", "Workspace"),
                    Crumb::new("projects", "Projects"),
                    Crumb::new("gpui-kit", "gpui-kit"),
                    Crumb::new("runs", "Runs"),
                    Crumb::new("run-4821", "Indexing"),
                ])
                .max_visible(3)
                .on_select(|_, _, _| {})
                .on_reveal(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn navigation_sections() -> Vec<SidebarSection> {
    vec![
        SidebarSection::new("work").title("Work").items([
            SidebarItem::new("runs", "Runs")
                .icon(Icon::List)
                .badge("12")
                .children([
                    SidebarItem::new("runs.active", "Active").icon(Icon::Refresh),
                    SidebarItem::new("runs.archived", "Archived").icon(Icon::Archive),
                ]),
            SidebarItem::new("files", "Files").icon(Icon::Folder),
        ]),
        SidebarSection::new("admin").title("Administration").items([
            SidebarItem::new("settings", "Settings").icon(Icon::Settings),
            SidebarItem::new("policy", "Managed by policy")
                .icon(Icon::Key)
                .disabled(true),
        ]),
    ]
}

#[derive(Default)]
struct SceneSidebar {
    active: Option<SharedString>,
    collapsed: bool,
}

impl Global for SceneSidebar {}

pub(super) fn sidebar(_window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneSidebar>() {
        cx.set_global(SceneSidebar::default());
    }
    let theme = cx.theme().clone();
    let active = cx
        .global::<SceneSidebar>()
        .active
        .clone()
        .unwrap_or_else(|| "runs.active".into());
    let collapsed = cx.global::<SceneSidebar>().collapsed;
    let rail = |ident: &'static str, collapsed: bool| {
        Sidebar::new(ident)
            .sections(navigation_sections())
            .active(active.clone())
            .collapsed(collapsed)
            .footer(
                crate::foundation::text(
                    &theme,
                    TypeScale::Caption,
                    if collapsed {
                        SharedString::new_static("v0")
                    } else {
                        SharedString::new_static("Fixture workspace")
                    },
                )
                .text_tone(&theme, TextTone::Faint),
            )
            .on_select(|id, window, cx| {
                cx.global_mut::<SceneSidebar>().active = Some(id);
                window.refresh();
            })
    };

    stack(&theme)
        .child(caption(
            &theme,
            "Fixture navigation — select a destination, toggle a branch, or collapse the rail",
        ))
        .child(
            div()
                .flex()
                .flex_row()
                .h(px(400.0))
                .gap(px(theme.space(Space::Lg)))
                .child(
                    rail("scene.sidebar.expanded", collapsed)
                        .width(260.0)
                        .header(caption(
                            &theme,
                            if collapsed { "W" } else { "Fixture workspace" },
                        ))
                        .on_collapse(|collapsed, window, cx| {
                            cx.global_mut::<SceneSidebar>().collapsed = collapsed;
                            window.refresh();
                        }),
                )
                .child(rail("scene.sidebar.collapsed", true)),
        )
        .child(caption(
            &theme,
            "Current destination is caller-owned; arrow keys move focus without navigating",
        ))
        .into_any_element()
}

pub(super) fn sidebar_overflow(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .child(caption(&theme, "Fixture stress states — fixed slots, scrolling, long labels, and a closed current branch"))
        .child(div().flex().gap(px(theme.space(Space::Lg))).h(px(320.0))
            .child(div().w(px(300.0)).h_full().child(Sidebar::new("scene.sidebar-overflow.scroll")
                .fill_width()
                .header(caption(&theme, "Fixed header"))
                .footer(caption(&theme, "Fixed footer"))
                .section(SidebarSection::new("destinations").title("Destinations").items(
                    (0..24).map(|index| SidebarItem::new(format!("destination-{index}"),
                        if index == 1 { "A long destination label that needs an ellipsis".to_owned() }
                        else { format!("Fixture destination {}", index + 1) }).disabled(index == 2))
                ))
                .active("destination-1")
                .on_select(|_, _, _| {})))
            .child(Sidebar::new("scene.sidebar-overflow.closed")
                .sections(navigation_sections()).expanded_ids(&[])
                .active("runs.active")
                .header(caption(&theme, "Host refuses branch expansion"))
                .footer(caption(&theme, "Current: Active"))
                .on_select(|_, _, _| {}).on_toggle(|_, _, _, _| {})))
        .into_any_element()
}

/// The page-size control of the pagination scene, kept across frames.
pub(super) struct ScenePagination {
    page_size: Entity<Select>,
}

impl Global for ScenePagination {}

pub(super) fn pagination(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<ScenePagination>() {
        let page_size = cx.new(|cx| {
            Select::new("scene.pagination.size", window, cx)
                .name("Rows per page")
                .options([
                    SelectOption::new("25", "25 per page"),
                    SelectOption::new("50", "50 per page"),
                    SelectOption::new("100", "100 per page"),
                ])
                .selected("50")
        });
        cx.set_global(ScenePagination { page_size });
    }
    let page_size = cx.global::<ScenePagination>().page_size.clone();
    let theme = cx.theme().clone();

    stack(&theme)
        .w(px(620.0))
        .child(caption(
            &theme,
            "a known total, with the page size the host offered",
        ))
        .child(
            Pagination::new("scene.pagination.known")
                .page(9)
                .total_pages(20)
                .page_size(page_size)
                .on_select(|_, _, _| {}),
        )
        // A host that only knows there is another page says exactly that: no
        // last-page control, no numbers, and no total.
        .child(caption(
            &theme,
            "an unknown total: no numbers, no last page, and no count invented",
        ))
        .child(
            Pagination::new("scene.pagination.unknown")
                .page(3)
                .unknown_total(true)
                .on_select(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn wizard(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(720.0))
        .child(caption(
            &theme,
            "horizontal, with a blocked step and a failed one",
        ))
        .child(
            Wizard::new("scene.wizard.release")
                .steps([
                    WizardStep::new("prepare", "Prepare")
                        .description("Check the workspace is clean")
                        .complete(),
                    WizardStep::new("build", "Build")
                        .description("Compile every target")
                        .failed("The build failed on the test target."),
                    WizardStep::new("sign", "Sign").current(),
                    WizardStep::new("publish", "Publish")
                        .blocked("Approval is required for this workspace."),
                ])
                .body(crate::foundation::text(
                    &theme,
                    TypeScale::Body,
                    SharedString::new_static("The body of the current step belongs to the caller."),
                ))
                .back_to("build")
                .on_navigate(|_, _, _| {}),
        )
        .child(caption(&theme, "vertical, finishing"))
        .child(
            Wizard::new("scene.wizard.setup")
                .vertical()
                .steps([
                    WizardStep::new("account", "Account").complete(),
                    WizardStep::new("workspace", "Workspace").complete(),
                    WizardStep::new("review", "Review").current(),
                ])
                .back_to("workspace")
                .finish(true)
                .on_navigate(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn undo_history(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(caption(
            &theme,
            "the caller owns every revision and whether it can be restored",
        ))
        .child(
            UndoHistory::new("scene.undo-history", "Document undo history")
                .entries([
                    HistoryEntry::new("opened", "Opened the fixture")
                        .description("The initial verified document")
                        .source("Fixture host")
                        .time("10:14"),
                    HistoryEntry::new("renamed", "Renamed the title")
                        .source("Alex")
                        .time("10:16"),
                    HistoryEntry::new("imported", "Imported archived blocks")
                        .description("The archive is no longer available")
                        .source("Archive")
                        .time("10:18")
                        .unavailable("This revision cannot be restored without the archive."),
                    HistoryEntry::new("current", "Reordered the summary")
                        .description("Current document")
                        .source("Alex")
                        .time("10:21"),
                    HistoryEntry::new("draft", "Drafted a new conclusion")
                        .source("Fixture host")
                        .time("10:23"),
                ])
                .current("current")
                .on_jump(|_, _, _| {}),
        )
        .into_any_element()
}

pub(super) fn collapsible(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(
            Collapsible::new("scene.collapsible.open", "Advanced")
                .description("Settings most runs never touch")
                .open(true)
                .body(crate::foundation::text(
                    &theme,
                    TypeScale::Body,
                    "Requests go out over the system proxy.",
                ))
                .on_toggle(|_, _, _| {}),
        )
        .child(
            Collapsible::new("scene.collapsible.shut", "Diagnostics")
                .description("Nothing is collected until this is opened")
                .body(crate::foundation::text(
                    &theme,
                    TypeScale::Body,
                    "This body is absent from the tree while it is shut.",
                ))
                .on_toggle(|_, _, _| {}),
        )
        .child(
            Collapsible::new("scene.collapsible.refused", "Managed by policy")
                .description("This machine cannot change these")
                .disabled(true)
                .body(crate::foundation::text(
                    &theme,
                    TypeScale::Body,
                    "Set by the administrator.",
                )),
        )
        .into_any_element()
}

//! Surfaces that appear above the page and take a decision.

use super::support::*;

pub(super) fn kbd(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    // A keystroke is only ever read beside the thing it performs, so the
    // exhibit shows it where it is used: at the end of a menu row, at the end
    // of a sentence, and in the run of special keys whose glyphs are the part
    // that goes wrong.
    let named = |label: &'static str, keystroke: &'static str, id: &'static str| {
        div()
            .row()
            .w_full()
            .items_center()
            .justify_between()
            .gap_token(&theme, Space::Md)
            .py_token(&theme, Space::Xs)
            .child(crate::foundation::text(&theme, TypeScale::Body, label))
            .child(Kbd::new(keystroke).id(id))
    };

    stack(&theme)
        .w(px(420.0))
        .child(caption(&theme, "the shortcut sits with the thing it does"))
        .child(
            div()
                .column()
                .w_full()
                .card_surface(&theme, CardVariant::Elevated)
                .px_token(&theme, Space::Md)
                .py_token(&theme, Space::Sm)
                .child(named(
                    "Open the command palette",
                    "cmd-shift-p",
                    "scene.kbd.palette",
                ))
                .child(div().w_full().h(px(theme.space(Space::Sm))))
                .child(named("Copy the selection", "ctrl-c", "scene.kbd.copy"))
                .child(div().w_full().h(px(theme.space(Space::Sm))))
                .child(named("Rename in place", "cmd-alt-r", "scene.kbd.rename")),
        )
        .child(caption(
            &theme,
            "the keys that need the bundled symbol face",
        ))
        .child(
            row(&theme)
                .gap_token(&theme, Space::Sm)
                .child(Kbd::new("enter").id("scene.kbd.confirm"))
                .child(Kbd::new("escape").id("scene.kbd.dismiss"))
                .child(Kbd::new("backspace").id("scene.kbd.erase"))
                .child(Kbd::new("delete").id("scene.kbd.delete"))
                .child(Kbd::new("tab").id("scene.kbd.advance"))
                .child(Kbd::new("space").id("scene.kbd.space"))
                .child(Kbd::new("up").id("scene.kbd.up"))
                .child(Kbd::new("down").id("scene.kbd.down")),
        )
        .child(
            div()
                .row()
                .items_center()
                .gap_token(&theme, Space::Xs)
                .child(crate::foundation::text(&theme, TypeScale::Body, "Press"))
                .child(Kbd::new("cmd-enter").id("scene.kbd.inline"))
                .child(crate::foundation::text(
                    &theme,
                    TypeScale::Body,
                    "to send the message.",
                ))
                .text_color(theme.colors.text_muted),
        )
        .into_any_element()
}

/// A page for a modal layer to sit over.
///
/// A scrim drawn over an empty canvas is indistinguishable from a fill: the
/// only thing that shows it is translucent is what stays legible underneath
/// it. So every scene in this file that raises a modal layer puts a page worth
/// obscuring behind it rather than one line of text.
fn page_behind(theme: &Theme, title: &'static str) -> gpui::Div {
    let card = |heading: &'static str, lines: usize| {
        div()
            .flex_1()
            .min_w_0()
            .card_surface(theme, CardVariant::Elevated)
            .overflow_hidden()
            .child(filler(theme, heading, lines))
    };
    div()
        .column()
        .w_full()
        .gap(px(theme.space(Space::Md)))
        .child(crate::foundation::text(theme, TypeScale::Subtitle, title))
        .child(
            div()
                .row()
                .w_full()
                .gap(px(theme.space(Space::Md)))
                .child(card("Runs", 5))
                .child(card("Details", 5)),
        )
}

pub(super) fn overlay(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .h(px(320.0))
        .child(page_behind(&theme, "Workspace settings"))
        .child(
            Overlay::modal("scene.overlay.dialog")
                .placement(Placement::Center)
                .child(
                    crate::overlay::surface(
                        "scene.overlay.surface",
                        &theme,
                        crate::overlay::OverlaySurface::MODAL,
                    )
                    .w(px(320.0))
                    .p(px(theme.spacing.lg))
                    .gap(px(theme.spacing.sm))
                    .child(crate::foundation::text(
                        &theme,
                        TypeScale::Subtitle,
                        "Delete this workspace?",
                    ))
                    .child(
                        crate::foundation::text(
                            &theme,
                            TypeScale::Body,
                            "Its runs, filters and saved views are removed for everyone. This \
                                 cannot be undone.",
                        )
                        .text_tone(&theme, TextTone::Muted),
                    )
                    .child(
                        div()
                            .row()
                            .justify_end()
                            .gap(px(theme.spacing.sm))
                            .child(
                                Button::new("scene.overlay.cancel")
                                    .label("Cancel")
                                    .secondary()
                                    .on_click(|_, _| {}),
                            )
                            .child(
                                Button::new("scene.overlay.confirm")
                                    .label("Delete")
                                    .danger()
                                    .on_click(|_, _| {}),
                            ),
                    ),
                ),
        )
        .into_any_element()
}

/// The dialog the scene shows, kept across frames.
///
/// A dialog owns whether it is open and which element had the keyboard before
/// it opened, so rebuilding it every frame would reopen it every frame.
pub(super) struct SceneDialog {
    replace: Entity<Dialog>,
}

impl Global for SceneDialog {}

pub(super) fn dialog(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneDialog>() {
        let replace = cx.new(|cx| {
            Dialog::new("scene.dialog.replace", window, cx)
                .title("Replace the existing theme?")
                .description(
                    "The application owns this decision. The dialog presents it and reports what \
                     was chosen.",
                )
                .cancel_label("Cancel")
                .confirm_label("Replace")
        });
        replace.update(cx, |dialog, cx| dialog.open(window, cx));
        cx.set_global(SceneDialog { replace });
    }
    let replace = cx.global::<SceneDialog>().replace.clone();
    let theme = cx.theme().clone();

    stack(&theme)
        .w(px(560.0))
        .h(px(360.0))
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "Content behind the dialog",
        ))
        .child(replace)
        .into_any_element()
}

pub(super) fn tooltip(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .child(
            row(&theme).child(
                div()
                    .id("scene.tooltip.host")
                    .tip("scene.tooltip.export", "Writes the theme to a file on disk")
                    .child(
                        Button::new("scene.tooltip.export")
                            .label("Export theme")
                            .accessible_description("Writes the theme to a file on disk")
                            .secondary()
                            .on_click(|_, _| {}),
                    ),
            ),
        )
        // Hover help only exists while a pointer rests on the control, so the
        // surface itself is also shown outright, where it can be reviewed.
        .child(
            row(&theme).child(
                Tooltip::new("scene.tooltip.help", "Writes the theme to a file on disk")
                    .describes("scene.tooltip.export"),
            ),
        )
        .into_any_element()
}

/// The menu family the scenes show, kept across frames.
///
/// Each of these owns whether it is open, where the keyboard is, and which
/// submenu stands expanded, so rebuilding them every frame would reopen them
/// every frame. Building them once is also what makes the capture static.
pub(super) struct SceneMenus {
    menu: Entity<Menu>,
    context: Entity<ContextMenu>,
    palette: Entity<CommandPalette>,
    popover: Entity<Popover>,
}

impl Global for SceneMenus {}

pub(super) fn menu_items() -> Vec<MenuItem> {
    vec![
        MenuItem::section("group", "This run"),
        MenuItem::command("copy", "Copy run id")
            .icon(Icon::Copy)
            .shortcut("cmd-c"),
        MenuItem::check("follow", "Follow output", true),
        MenuItem::separator("rule"),
        MenuItem::command("publish", "Publish").disabled(true),
        MenuItem::submenu(
            "share",
            "Share",
            [
                MenuItem::command("share.link", "Copy link").shortcut("cmd-shift-c"),
                MenuItem::command("share.export", "Export as file"),
            ],
        ),
        MenuItem::separator("destroy"),
        MenuItem::command("delete", "Delete this run")
            .icon(Icon::Trash)
            .destructive(true),
    ]
}

pub(super) fn scene_commands() -> Vec<Command> {
    vec![
        Command::new("workspace.open", "Open workspace")
            .section("Workspace")
            .shortcut("cmd-o"),
        Command::new("workspace.close", "Close workspace").section("Workspace"),
        Command::new("workspace.publish", "Publish workspace")
            .section("Workspace")
            .unavailable("Approval is required"),
        Command::new("editor.wrap", "Toggle word wrap").section("Editor"),
    ]
}

pub(super) fn ensure_menus(window: &mut Window, cx: &mut App) {
    if cx.has_global::<SceneMenus>() {
        return;
    }
    let menu = cx.new(|cx| {
        Menu::new("scene.menu.run", window, cx)
            .trigger("Run actions")
            .items(menu_items())
    });
    menu.update(cx, |menu, cx| {
        menu.open_submenu("share", window, cx);
    });

    // The catalog reviews the drawn surface. Left on `Native`, `open_at` would
    // hand this list to the operating system on macOS and Windows: a modal
    // popup no capture can hold, opened under every scene that shares these
    // fixtures, and on Windows it takes the keyboard from the `menu` scene's
    // exhibit while the native UIA check reads it.
    let context = cx.new(|cx| {
        ContextMenu::new("scene.context.run", window, cx)
            .name("Run actions")
            .target("run-a04")
            .presentation(ContextMenuPresentation::InWindow)
            .menu(menu_items())
            .content(|_, cx| {
                let theme = cx.theme().clone();
                div()
                    .w(px(320.0))
                    .p(px(theme.spacing.md))
                    .surface(&theme, Surface::Panel)
                    .radius(&theme, Radius::Card)
                    .child(crate::foundation::text(
                        &theme,
                        TypeScale::Body,
                        "Right-click this fixture row",
                    ))
                    .into_any_element()
            })
    });
    context.update(cx, |context, cx| {
        context
            .open_at(gpui::point(px(180.0), px(150.0)), window, cx)
            .expect("in-window fixture");
    });

    let palette = cx.new(|cx| {
        CommandPalette::new("scene.palette.commands", window, cx).commands(scene_commands())
    });
    palette.update(cx, |palette, cx| palette.set_query("work", cx));

    let popover = cx.new(|cx| {
        Popover::new("scene.popover.filters", window, cx)
            .trigger("Filters")
            .content(|_, cx| {
                let theme = cx.theme().clone();
                div()
                    .column()
                    .w(px(260.0))
                    .gap(px(theme.spacing.sm))
                    .child(crate::foundation::text(
                        &theme,
                        TypeScale::Body,
                        "Anything can live in a popover.",
                    ))
                    .child(
                        Checkbox::new("scene.popover.failing")
                            .label("Failing runs only")
                            .on_change(|_, _, _| {}),
                    )
                    .into_any_element()
            })
    });
    popover.update(cx, |popover, cx| popover.open(window, cx));

    cx.set_global(SceneMenus {
        menu,
        context,
        palette,
        popover,
    });
}

pub(super) fn popover(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_menus(window, cx);
    let popover = cx.global::<SceneMenus>().popover.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .h(px(320.0))
        // The trigger keeps its place while the surface is open, because the
        // surface is anchored to it rather than laid out beside it.
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "The trigger owns whether the surface is open.",
        ))
        .child(popover)
        .into_any_element()
}

pub(super) fn menu(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_menus(window, cx);
    let menus = cx.global::<SceneMenus>();
    let menu = menus.menu.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(560.0))
        .h(px(320.0))
        // An open surface is an overlay and takes no room in the flow, so the
        // scene reserves the panel's review area explicitly.
        .child(div().h(px(300.0)).child(menu))
        .into_any_element()
}

pub(super) fn context_menu(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_menus(window, cx);
    let context = cx.global::<SceneMenus>().context.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(560.0))
        .h(px(400.0))
        // Opening a context menu reports the row that was pointed at; what is
        // selected stays the host's answer.
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "The right-click reports the row. Nothing is selected by it.",
        ))
        .child(context)
        .into_any_element()
}

pub(super) fn command_palette(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_menus(window, cx);
    let palette = cx.global::<SceneMenus>().palette.clone();
    let theme = cx.theme().clone();
    // A palette is summoned over whatever is on screen, so it sits centred
    // near the top of the surface rather than in a corner of it.
    stack(&theme)
        .w_full()
        .h(px(420.0))
        .items_center()
        .pt(px(theme.spacing.xxl))
        .child(palette)
        .into_any_element()
}

/// The notification layer the scene shows, kept across frames.
///
/// The stack, each timer, and each entry animation outlive a frame, so the
/// layer is built once and the toasts are pushed once with it.
pub(super) struct SceneToasts {
    layer: Entity<ToastLayer>,
}

impl Global for SceneToasts {}

pub(super) fn toast(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneToasts>() {
        let layer = cx.new(|cx| ToastLayer::new(window, cx).capacity(4));
        cx.set_global(SceneToasts { layer });
        toast_push(
            window,
            cx,
            Toast::new("scene.toast.saved", "Theme exported to disk").tone(Tone::Success),
        );
        toast_push(
            window,
            cx,
            Toast::new("scene.toast.stale", "Refreshing the model catalog failed")
                .tone(Tone::Warning)
                .detail("The last verified catalog is still shown."),
        );
        toast_push(
            window,
            cx,
            // A refusal, so the offer is the thing that could change the
            // answer. Retrying an unapproved call only gets refused again.
            Toast::new(
                "scene.toast.refused",
                "The host refused to publish this run",
            )
            .tone(Tone::Warning)
            .detail("Approval is required for this workspace.")
            .action("Request approval", |_, _| {}),
        );
        // The failure beside the refusal, so the two tones are on screen
        // together and the difference between them is a picture rather than a
        // claim.
        toast_push(
            window,
            cx,
            Toast::new("scene.toast.failed", "Publishing this run failed")
                .tone(Tone::Danger)
                .detail("The publish service did not respond.")
                .action("Try again", |_, _| {}),
        );
    }
    let layer = cx.global::<SceneToasts>().layer.clone();
    let theme = cx.theme().clone();

    stack(&theme)
        .w(px(560.0))
        .h(px(360.0))
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "Content behind the notifications",
        ))
        // A failure keeps its report on screen; only the success times out.
        .child(crate::foundation::text(
            &theme,
            TypeScale::Body,
            "A danger or warning toast stays until it is dismissed.",
        ))
        .child(layer)
        .into_any_element()
}

/// The notification centre the scene shows, kept across frames.
pub(super) struct SceneNotifications {
    centre: Entity<NotificationCenter>,
}

impl Global for SceneNotifications {}

pub(super) fn notification_center(_window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneNotifications>() {
        let centre = cx.new(|cx| NotificationCenter::new("scene.notifications", cx));
        centre.update(cx, |centre, cx| {
            centre.record(
                Notification::new("scene.notify.exported", "Theme exported to disk")
                    .tone(Tone::Success)
                    .at("9:41")
                    .read(true),
                cx,
            );
            centre.record(
                Notification::new("scene.notify.stale", "Refreshing the model catalog failed")
                    .tone(Tone::Warning)
                    .detail("The last verified catalog is still shown.")
                    .at("9:44"),
                cx,
            );
            centre.record(
                Notification::new(
                    "scene.notify.refused",
                    "The host refused to publish this run",
                )
                .tone(Tone::Warning)
                .detail("Approval is required for this workspace.")
                .at("9:46")
                .action("Request approval", |_, _| {}),
                cx,
            );
        });
        cx.set_global(SceneNotifications { centre });
    }
    let centre = cx.global::<SceneNotifications>().centre.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        // The same three reports the toast scene shows, after their toasts
        // have gone.
        .child(caption(
            &theme,
            "what the toasts said, still here once they timed out",
        ))
        .child(centre)
        .into_any_element()
}

/// Glass over the page it covers, which is the only way to see that the
/// backdrop is blurred rather than merely tinted.
pub(super) fn frost(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let card = |title: &'static str, body: &'static str| {
        div()
            .column()
            .gap(px(theme.space(Space::Xs)))
            .p(px(theme.space(Space::Md)))
            .child(crate::foundation::text(
                &theme,
                TypeScale::Label,
                SharedString::from(title),
            ))
            .child(caption(&theme, body))
    };
    // What is behind the glass is a page, not a test pattern. A block of
    // accent stripes dropped into a panel reads as a hole cut in it, and it
    // answers the wrong question anyway: the thing a reader has to be able to
    // judge is whether text they can otherwise read has gone out of focus.
    stack(&theme)
        .w(px(480.0))
        .child(caption(&theme, "A floating surface on glass"))
        .child(
            div()
                .relative()
                .h(px(200.0))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(filler(&theme, "Document", 8))
                .child(
                    div()
                        .absolute()
                        .top(px(48.0))
                        // Far enough left to cross the lines. Placed clear of
                        // them the glass had nothing behind it, so the picture
                        // could not answer the one question it is here for:
                        // whether text a reader can otherwise read has gone
                        // out of focus.
                        .left(px(64.0))
                        .w(px(240.0))
                        .child(
                            Frost::new("scene.frost.popover")
                                .radius(Radius::Card)
                                .child(card(
                                    "Rename",
                                    "The page behind stays visible, out of focus",
                                )),
                        ),
                ),
        )
        .child(caption(
            &theme,
            "The same glass, blurred further and raised above the panel",
        ))
        .child(
            div()
                .relative()
                .h(px(160.0))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(filler(&theme, "Files", 6))
                .child(
                    div()
                        .absolute()
                        .top(px(32.0))
                        .left(px(64.0))
                        .w(px(280.0))
                        .child(
                            Frost::new("scene.frost.rail")
                                // A surface a step above the page it covers.
                                // Tinted with the page's own colour the glass
                                // had no surface of its own: at any alpha, a
                                // fill the colour of what is behind it adds
                                // nothing, and the rail's words landed
                                // directly on the words of the file list.
                                .surface(gpui_kit_theme::Surface::Raised)
                                .radius(Radius::Dialog)
                                .blur(32.0)
                                .child(card(
                                    "Rail",
                                    "The lines behind stay legible and out of focus",
                                )),
                        ),
                ),
        )
        .into_any_element()
}

/// The optics, one at a time and then together, over a backdrop with enough
/// structure in it that a bend is visible as a bend rather than as a smudge.
///
/// The presets sit side by side deliberately: `Frosted` is the control, and
/// what separates it from `Lens` is exactly the refraction, so a reviewer
/// looking at the pair is looking at the thing that changed.
/// One square of the backdrop every optics plate is read against, and the two
/// plate footprints built from it. Both are whole numbers of squares.
const TILE: f32 = 48.0;
const PLATE_WIDTH: f32 = TILE * 8.0;
const PLATE_HEIGHT: f32 = TILE * 3.0;
const JOIN_WIDTH: f32 = PLATE_WIDTH;

/// A deterministic raster media fixture, not downloaded product content.
fn glass_media() -> Arc<RenderImage> {
    static IMAGE: OnceLock<Arc<RenderImage>> = OnceLock::new();
    IMAGE
        .get_or_init(|| {
            let mut pixels = Vec::with_capacity(480 * 144 * 4);
            for y in 0..144 {
                for x in 0..480 {
                    let ridge = 65 + ((x as f32 * 0.018).sin() * 30.0) as i32;
                    let rgba = if y > ridge {
                        [28, 91 + (x / 8) as u8, 91, 255]
                    } else {
                        [110 + (y / 2) as u8, 165, 220, 255]
                    };
                    pixels.extend_from_slice(&rgba);
                }
            }
            Arc::new(
                RenderImage::from_rgba(size(DevicePixels(480), DevicePixels(144)), pixels)
                    .expect("media fixture dimensions"),
            )
        })
        .clone()
}

/// A constrained media tile owns size; its caption only names edge anchors.
pub(super) fn media_caption(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let tile = |id: &'static str, reduced: bool| {
        let material_theme = theme.clone().with_reduce_transparency(reduced);
        div()
            .relative()
            .w_full()
            .h(px(220.0))
            .radius(&theme, Radius::Card)
            .overflow_hidden()
            .child(
                gpui::img(glass_media())
                    .size_full()
                    .radius(&theme, Radius::Card)
                    .object_fit(gpui::ObjectFit::Cover),
            )
            .child(
                crate::overlay::surface(
                    id,
                    &material_theme,
                    crate::overlay::OverlaySurface::MEDIA_CAPTION,
                )
                .absolute()
                .left_0()
                .right_0()
                .bottom_0()
                .flex_row()
                .items_center()
                .gap_token(&theme, Space::Md)
                .p_token(&theme, Space::Md)
                .child(
                    div()
                        .flex_1()
                        .min_w_0()
                        .type_scale(&theme, TypeScale::Label)
                        .child(if reduced {
                            "Media fixture · reduced transparency"
                        } else {
                            "Media fixture · Clear caption"
                        }),
                )
                .child(
                    div()
                        .type_scale(&theme, TypeScale::Caption)
                        .child("3 projects"),
                )
                .child(
                    Button::new(format!("{id}.new"))
                        .label("New project")
                        .ghost()
                        .on_click(|_, _| {}),
                ),
            )
    };
    stack(&theme)
        .w_full()
        .child(caption(
            &theme,
            "Parent-constrained width; caption height follows its content",
        ))
        .child(tile("scene.media-caption.clear", false))
        .child(tile("scene.media-caption.reduced", true))
        .into_any_element()
}

pub(super) fn glass(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let label = |title: &'static str, body: &'static str| {
        div()
            .column()
            .gap(px(theme.space(Space::Xs)))
            .p(px(theme.space(Space::Md)))
            .child(div().type_scale(&theme, TypeScale::Label).child(title))
            .child(div().type_scale(&theme, TypeScale::Caption).child(body))
    };

    // A ruled checkerboard makes the material contract visible: Regular's
    // rim refracts the scattered source, bending colour and luminance bands
    // without restoring recognizable fine lines. Clear stays sharp because
    // its blur is zero. The board is neutral, and every plate is an exact
    // number of squares across and down: a board cut through the middle of a
    // square at the plate edge reads as a broken pattern rather than as the
    // ruled backdrop the optics are being measured against.
    let checker_light = theme.colors.text_faint;
    let checker_dark = theme.colors.canvas;
    let checker_rule = theme.colors.divider.opacity(0.55);
    let checkerboard =
        |width: f32, height: f32| {
            let columns = (width / TILE).ceil() as usize;
            let rows = (height / TILE).ceil() as usize;
            div()
                .absolute()
                .top(px(0.0))
                .left(px(0.0))
                .w(px(width))
                .h(px(height))
                .column()
                .overflow_hidden()
                .children((0..rows).map(|row| {
                    div()
                        .flex()
                        .flex_none()
                        .h(px(TILE))
                        .children((0..columns).map(move |column| {
                            div().flex_none().w(px(TILE)).h(px(TILE)).bg(
                                if (row + column) % 2 == 0 {
                                    checker_light
                                } else {
                                    checker_dark
                                },
                            )
                        }))
                }))
                .children((0..=(width / 12.0) as usize).map(|column| {
                    div()
                        .absolute()
                        .top(px(0.0))
                        .left(px(column as f32 * 12.0))
                        .w(px(1.0))
                        .h(px(height))
                        .bg(checker_rule)
                }))
                .children((0..=(height / 12.0) as usize).map(|row| {
                    div()
                        .absolute()
                        .top(px(row as f32 * 12.0))
                        .left(px(0.0))
                        .w(px(width))
                        .h(px(1.0))
                        .bg(checker_rule)
                }))
        };

    let plate =
        |ident: &'static str, preset: GlassPreset, title: &'static str, body: &'static str| {
            div()
                .relative()
                .h(px(PLATE_HEIGHT))
                .w(px(PLATE_WIDTH))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(
                    if preset == GlassPreset::Clear || ident == "scene.glass.media" {
                        gpui::img(glass_media())
                            .object_fit(gpui::ObjectFit::Cover)
                            .w(px(PLATE_WIDTH))
                            .h(px(PLATE_HEIGHT))
                            .into_any_element()
                    } else {
                        checkerboard(PLATE_WIDTH, PLATE_HEIGHT).into_any_element()
                    },
                )
                .when(ident == "scene.glass.liquid", |element| {
                    element.child(div().absolute().inset_0().child(filler(
                        &theme,
                        "Document text behind Regular glass",
                        6,
                    )))
                })
                .child(
                    div()
                        .absolute()
                        .top(px(28.0))
                        .left(px(52.0))
                        .w(px(280.0))
                        .child(
                            Glass::new(ident)
                                .preset(preset)
                                .dimmed(preset == GlassPreset::Clear)
                                .adaptive(true)
                                .radius(Radius::Dialog)
                                .child(label(title, body)),
                        ),
                )
        };

    stack(&theme)
        .w(px(900.0))
        .child(caption(
            &theme,
            "Clear: dark Frosted / reduced transparency           Clear + 35% dimming / media only",
        ))
        .child(
            row(&theme)
                .child(crate::foundation::ThemeOverlay::theme(
                    theme.clone().with_reduce_transparency(true),
                    plate(
                        "scene.glass.frosted",
                        GlassPreset::Clear,
                        "Clear: reduced transparency",
                        "Dark Frosted and light content in every theme",
                    ),
                ))
                .child(plate(
                    "scene.glass.clear",
                    GlassPreset::Clear,
                    "Clear + dimmed",
                    "A sharp media fixture, never default chrome",
                )),
        )
        .child(caption(
            &theme,
            "Regular over text and media: blur, achromatic wash, lensing and hairline",
        ))
        .child(
            row(&theme)
                .child(plate(
                    "scene.glass.liquid",
                    GlassPreset::Liquid,
                    "Regular Liquid",
                    "Large reading surfaces keep the window appearance",
                ))
                .child(plate(
                    "scene.glass.media",
                    GlassPreset::Liquid,
                    "Regular on media",
                    "The same material, not the Clear variant",
                )),
        )
        .child(caption(
            &theme,
            "Compact Regular controls: appearance may flip; no opaque face",
        ))
        .child(
            div()
                .relative()
                .h(px(TILE * 2.0))
                .w(px(PLATE_WIDTH))
                .surface(&theme, Surface::Panel)
                .radius(&theme, Radius::Card)
                .overflow_hidden()
                .child(div().absolute().inset_0().bg(gpui::white()))
                .child(
                    div()
                        .absolute()
                        .right_0()
                        .top_0()
                        .w(px(PLATE_WIDTH / 2.0))
                        .h_full()
                        .bg(gpui::black()),
                )
                .child(
                    div()
                        .absolute()
                        .inset_0()
                        .flex()
                        .items_center()
                        .justify_center()
                        .gap(px(theme.space(Space::Sm)))
                        .child(
                            Glass::new("scene.glass.regular.one")
                                .preset(GlassPreset::Liquid)
                                .surface(Surface::Raised)
                                .radius(Radius::Pill)
                                .adaptive(true)
                                .child(
                                    div()
                                        .w(px(110.0))
                                        .h(px(32.0))
                                        .flex()
                                        .items_center()
                                        .justify_center()
                                        .child("Portal"),
                                ),
                        )
                        .child(
                            Glass::new("scene.glass.regular.two")
                                .preset(GlassPreset::Liquid)
                                .radius(Radius::Pill)
                                .adaptive(true)
                                .child(
                                    div()
                                        .w(px(110.0))
                                        .h(px(32.0))
                                        .rounded(px(theme.radius(Radius::Pill)))
                                        .bg(theme.colors.selected)
                                        .flex()
                                        .items_center()
                                        .justify_center()
                                        .child("Harbour"),
                                ),
                        ),
                ),
        )
        // The last two demonstrations sit side by side so the whole scene
        // stays inside the window a real display can give the gallery, which
        // is where the DirectX renderer gets looked at.
        .child(
            div()
                .flex()
                .flex_row()
                .gap(px(theme.space(Space::Md)))
                .child(
                    div()
                        .column()
                        .gap(px(theme.space(Space::Sm)))
                        .child(caption(&theme, "Fused: two panes joined into one body"))
                        .child(
                            div()
                                .relative()
                                .h(px(PLATE_HEIGHT))
                                .w(px(JOIN_WIDTH))
                                .surface(&theme, Surface::Panel)
                                .radius(&theme, Radius::Card)
                                .overflow_hidden()
                                .child(checkerboard(JOIN_WIDTH, PLATE_HEIGHT))
                                .child(
                                    div().absolute().top(px(40.0)).left(px(40.0)).child(
                                        GlassGroup::new("scene.glass.fused")
                                            .preset(GlassPreset::Liquid)
                                            .radius(Radius::Dialog)
                                            .gap(12.0)
                                            .pane(
                                                "scene.glass.fused.left",
                                                label("Left", "One lobe of the body"),
                                            )
                                            .pane(
                                                "scene.glass.fused.right",
                                                label("Right", "Joined across the gap"),
                                            ),
                                    ),
                                ),
                        ),
                )
                .child(
                    div()
                        .column()
                        .gap(px(theme.space(Space::Sm)))
                        .child(caption(&theme, "Large adaptive surfaces never flip"))
                        .child(
                            div()
                                .relative()
                                .h(px(PLATE_HEIGHT))
                                .w(px(JOIN_WIDTH))
                                .surface(&theme, Surface::Panel)
                                .radius(&theme, Radius::Card)
                                .overflow_hidden()
                                .child(
                                    div()
                                        .absolute()
                                        .top(px(0.0))
                                        .left(px(0.0))
                                        .w(px(JOIN_WIDTH / 2.0))
                                        .h(px(PLATE_HEIGHT))
                                        .bg(gpui::white()),
                                )
                                .child(
                                    div()
                                        .absolute()
                                        .top(px(0.0))
                                        .left(px(JOIN_WIDTH / 2.0))
                                        .w(px(JOIN_WIDTH / 2.0))
                                        .h(px(PLATE_HEIGHT))
                                        .bg(gpui::black()),
                                )
                                .child(
                                    div()
                                        .absolute()
                                        .top(px(28.0))
                                        .left(px(15.0))
                                        .w(px(160.0))
                                        .child(
                                            Glass::new("scene.glass.adaptive.bright")
                                                .preset(GlassPreset::Liquid)
                                                .radius(Radius::Dialog)
                                                .adaptive(true)
                                                .adaptive_appearance(true)
                                                .child(label(
                                                    "Bright",
                                                    "The reading lands next frame",
                                                )),
                                        ),
                                )
                                .child(
                                    div()
                                        .absolute()
                                        .top(px(28.0))
                                        .left(px(JOIN_WIDTH / 2.0 + 15.0))
                                        .w(px(160.0))
                                        .child(
                                            Glass::new("scene.glass.adaptive.dark")
                                                .preset(GlassPreset::Liquid)
                                                .radius(Radius::Dialog)
                                                .adaptive(true)
                                                .child(label("Dark", "The same glass, other side")),
                                        ),
                                ),
                        ),
                ),
        )
        .child(caption(
            &theme,
            "Focused: one inward report edge — Regular, Clear + dimmed, Frosted, reduced",
        ))
        .child(
            row(&theme).children(
                [
                    ("regular", GlassPreset::Liquid, false, "Regular"),
                    ("clear", GlassPreset::Clear, false, "Clear + dimmed"),
                    ("frosted", GlassPreset::Frosted, false, "Frosted"),
                    ("reduced", GlassPreset::Clear, true, "Reduced"),
                ]
                .into_iter()
                .map(|(name, preset, reduced, title)| {
                    div()
                        .relative()
                        .w(px(210.0))
                        .h(px(96.0))
                        .radius(&theme, Radius::Card)
                        .overflow_hidden()
                        .child(
                            gpui::img(glass_media())
                                .size_full()
                                .object_fit(gpui::ObjectFit::Cover),
                        )
                        .child(
                            div()
                                .absolute()
                                .left(px(12.0))
                                .right(px(12.0))
                                .top(px(24.0))
                                .child(crate::foundation::ThemeOverlay::theme(
                                    theme.clone().with_reduce_transparency(reduced),
                                    Glass::new(format!("scene.glass.focused.{name}"))
                                        .preset(preset)
                                        .dimmed(preset == GlassPreset::Clear)
                                        .focused(true)
                                        .radius(Radius::Pill)
                                        .child(
                                            div()
                                                .w_full()
                                                .h(px(40.0))
                                                .flex()
                                                .items_center()
                                                .justify_center()
                                                .type_scale(&theme, TypeScale::Label)
                                                .child(title),
                                        ),
                                )),
                        )
                }),
            ),
        )
        .child(caption(
            &theme,
            "Budget fallback: opaque cards, never holes — R keeps its focus report",
        ))
        .child(div().flex().gap(px(4.0)).children(('A'..='R').map(|name| {
            Glass::new(format!("scene.glass.budget.{name}"))
                .focused(name == 'R')
                .child(
                    div()
                        .w(px(38.0))
                        .h(px(30.0))
                        .flex()
                        .items_center()
                        .justify_center()
                        .child(name.to_string()),
                )
        })))
        .child(caption(
            &theme,
            "Nine lobes exceed the fused budget: every pane retains an opaque face",
        ))
        .child(
            ('A'..='I').fold(GlassGroup::new("scene.glass.lobe-budget"), |group, name| {
                group.pane(
                    format!("scene.glass.lobe-budget.{name}"),
                    div()
                        .w(px(58.0))
                        .h(px(30.0))
                        .flex()
                        .items_center()
                        .justify_center()
                        .child(name.to_string()),
                )
            }),
        )
        .into_any_element()
}

/// Optical parameters over a deterministic ruled fixture, without a wash or
/// decorative highlight hiding the displacement. Labels sit outside the lens.
pub(super) fn glass_optics(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let specimens = [
        ("identity", "Index 1 / unchanged", 1., 24., 24., 0., 0.),
        ("water", "Index 1.33 / shallow bend", 1.33, 24., 24., 0., 0.),
        ("glass", "Index 1.5 / deeper bend", 1.5, 24., 24., 0., 0.),
        (
            "thick",
            "48px thickness / same index",
            1.5,
            48.,
            24.,
            0.,
            0.,
        ),
        ("contact", "Source plane at base", 1.5, 24., 0., 0., 0.),
        (
            "dispersion",
            "Spectral indices / no highlight",
            1.5,
            24.,
            24.,
            0.25,
            0.,
        ),
        (
            "reflection",
            "Fresnel / same surface normal",
            1.5,
            24.,
            24.,
            0.,
            1.,
        ),
        (
            "scatter",
            "Same optics / 8px scattering",
            1.5,
            24.,
            24.,
            0.,
            0.,
        ),
    ];
    stack(&theme)
        .w(px(900.))
        .child(caption(
            &theme,
            "Snell refraction — fixture grid; foreground content is not distorted",
        ))
        .children(specimens.chunks(4).map(|specimens| {
            row(&theme).children(specimens.iter().map(
                |&(name, title, index, thickness, depth, dispersion, specular)| {
                    div()
                        .column()
                        .gap_token(&theme, Space::Sm)
                        .w(px(210.))
                        .child(caption(&theme, title))
                        .child(
                            div()
                                .relative()
                                .w(px(210.))
                                .h(px(180.))
                                .overflow_hidden()
                                .bg(theme.colors.canvas)
                                .children((0..18).map(|line| {
                                    div()
                                        .absolute()
                                        .top(px(line as f32 * 10.))
                                        .left_0()
                                        .w_full()
                                        .h(px(1.))
                                        .bg(theme.colors.text_faint)
                                }))
                                .children((0..21).map(|line| {
                                    div()
                                        .absolute()
                                        .left(px(line as f32 * 10.))
                                        .top_0()
                                        .h_full()
                                        .w(px(1.))
                                        .bg(theme.colors.text_faint)
                                }))
                                .child(
                                    div()
                                        .absolute()
                                        .top(px(22.))
                                        .left(px(25.))
                                        .w(px(160.))
                                        .child(
                                            Glass::new(format!("scene.glass-optics.{name}"))
                                                .preset(GlassPreset::Lens)
                                                .radius_px(48.)
                                                .refraction(1.)
                                                .thickness(thickness)
                                                .refractive_index(index)
                                                .backdrop_depth(depth)
                                                .dispersion(dispersion)
                                                .specular(specular)
                                                .blur(if name == "scatter" { 8. } else { 0. })
                                                .track_pointer(name == "reflection")
                                                .pressable(true)
                                                .child(
                                                    div()
                                                        .w_full()
                                                        .h(px(136.))
                                                        .flex()
                                                        .items_center()
                                                        .justify_center()
                                                        .child(
                                                            div()
                                                                .px_token(&theme, Space::Sm)
                                                                .bg(theme.colors.panel)
                                                                .child("Aa 123"),
                                                        ),
                                                ),
                                        ),
                                ),
                        )
                },
            ))
        }))
        .child(caption(
            &theme,
            "Fused Regular / zero blur — one height field; press changes thickness and its normal",
        ))
        .child(
            div()
                .relative()
                .w(px(864.))
                .h(px(120.))
                .overflow_hidden()
                .children((0..87).map(|line| {
                    div()
                        .absolute()
                        .left(px(line as f32 * 10.))
                        .top_0()
                        .h_full()
                        .w(px(1.))
                        .bg(theme.colors.text_faint)
                }))
                .children((0..12).map(|line| {
                    div()
                        .absolute()
                        .top(px(line as f32 * 10.))
                        .left_0()
                        .w_full()
                        .h(px(1.))
                        .bg(theme.colors.text_faint)
                }))
                .child(
                    div().absolute().left(px(24.)).top(px(24.)).child(
                        GlassGroup::new("scene.glass-optics.fused")
                            .preset(GlassPreset::Liquid)
                            .blur(0.)
                            .thickness(48.)
                            .refractive_index(1.5)
                            .backdrop_depth(24.)
                            .radius(Radius::Pill)
                            .gap(8.)
                            .merge(32.)
                            .pressable(true)
                            .pane(
                                "scene.glass-optics.fused.left",
                                div()
                                    .w(px(380.))
                                    .h(px(72.))
                                    .flex()
                                    .items_center()
                                    .justify_center()
                                    .child("Left"),
                            )
                            .pane(
                                "scene.glass-optics.fused.right",
                                div()
                                    .w(px(380.))
                                    .h(px(72.))
                                    .flex()
                                    .items_center()
                                    .justify_center()
                                    .child("Right"),
                            ),
                    ),
                ),
        )
        .into_any_element()
}

/// Colour belongs to the optical material, including a group's bridge.
/// Captions remain outside the specimens so tint strength is visible.
pub(super) fn glass_materials(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let specimens = [
        ("protected", "Default body protection", true, None),
        ("material", "Caller-owned legibility", false, None),
        (
            "tint-partial",
            "25% tint / visible backdrop",
            true,
            Some(0.25),
        ),
        (
            "tint-solid",
            "100% tint / rim above colour",
            true,
            Some(1.0),
        ),
    ];
    let backdrop = || {
        div()
            .relative()
            .w(px(200.0))
            .h(px(112.0))
            .bg(gpui::rgb(0xffffff))
            .children((0..8).map(|stripe| {
                div()
                    .absolute()
                    .left(px(stripe as f32 * 25.0))
                    .top_0()
                    .w(px(12.0))
                    .h_full()
                    .bg(gpui::rgb(0x8090a0))
            }))
    };
    stack(&theme)
        .w(px(900.0))
        .child(caption(
            &theme,
            "Material policy / deterministic stripe fixture, not native reference",
        ))
        .child(
            row(&theme).children(specimens.into_iter().map(|(id, title, protect, tint)| {
                let mut glass = Glass::new(format!("scene.glass-materials.{id}"))
                    .radius_px(24.0)
                    .blur(2.0)
                    .protect_text_contrast(protect);
                if let Some(alpha) = tint {
                    glass = glass.tint(theme.colors.accent.opacity(alpha));
                }
                div()
                    .column()
                    .gap_token(&theme, Space::Sm)
                    .child(caption(&theme, title))
                    .child(
                        backdrop().child(
                            div()
                                .absolute()
                                .left(px(20.0))
                                .top(px(24.0))
                                .child(glass.child(div().w(px(160.0)).h(px(64.0)))),
                        ),
                    )
            })),
        )
        .child(caption(
            &theme,
            "Tint across joined panes / reduced-transparency fallback",
        ))
        .child(
            row(&theme).children([false, true].into_iter().map(|reduced| {
                let id = if reduced { "reduced" } else { "joined" };
                let tint = theme.colors.accent.opacity(0.25);
                let group = GlassGroup::new(format!("scene.glass-materials.{id}"))
                    .radius(Radius::Pill)
                    .gap(8.0)
                    .merge(32.0)
                    .blur(2.0)
                    .tint(tint)
                    .pane(
                        format!("scene.glass-materials.{id}.left"),
                        div().w(px(68.0)).h(px(56.0)),
                    )
                    .pane(
                        format!("scene.glass-materials.{id}.right"),
                        div().w(px(68.0)).h(px(56.0)),
                    );
                backdrop().child(div().absolute().left(px(28.0)).top(px(28.0)).child(
                    ThemeOverlay::new(move |t| t.clone().with_reduce_transparency(reduced), group),
                ))
            })),
        )
        .child(caption(
            &theme,
            "Rounded foreground clips / surface shadows and joined optics stay outside",
        ))
        .child(
            row(&theme)
                .child(
                    backdrop().child(
                        div().absolute().left(px(20.0)).top(px(24.0)).child(
                            Glass::new("scene.glass-materials.clip-surface")
                                .radius_px(24.0)
                                .child(div().w(px(160.0)).h(px(64.0)).bg(theme.colors.accent)),
                        ),
                    ),
                )
                .child(
                    backdrop().child(
                        div().absolute().left(px(28.0)).top(px(28.0)).child(
                            GlassGroup::new("scene.glass-materials.clip-group")
                                .radius(Radius::Pill)
                                .gap(8.0)
                                .merge(32.0)
                                .pane(
                                    "scene.glass-materials.clip-left",
                                    div().w(px(68.0)).h(px(56.0)).bg(theme.colors.accent),
                                )
                                .pane(
                                    "scene.glass-materials.clip-right",
                                    div().w(px(68.0)).h(px(56.0)).bg(theme.colors.accent),
                                ),
                        ),
                    ),
                )
                .child(
                    backdrop().child(
                        div().absolute().left(px(20.0)).top(px(24.0)).child(
                            crate::overlay::surface(
                                "scene.glass-materials.clip-frame",
                                &theme,
                                crate::overlay::OverlaySurface::MODAL,
                            )
                            .w(px(160.0))
                            .h(px(64.0))
                            .children(
                                [theme.colors.accent, theme.colors.text_faint]
                                    .map(|color| div().w_full().h(px(32.0)).bg(color)),
                            ),
                        ),
                    ),
                ),
        )
        .into_any_element()
}

/// The drawer the scene shows, kept across frames and settled so the capture
/// photographs the panel where it comes to rest rather than mid-slide.
pub(super) struct SceneDrawer {
    filters: Entity<Drawer>,
}

impl Global for SceneDrawer {}

pub(super) fn drawer(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneDrawer>() {
        let filters = cx.new(|cx| {
            Drawer::new("scene.drawer.filters", window, cx)
                .edge(Edge::Right)
                .size(320.0)
                .resizable(true)
                .title("Filter runs")
                .description("The drawer reports what was chosen. The host applies it.")
                .content(|_, cx| {
                    let theme = cx.theme().clone();
                    let group = |heading: &'static str, rows: gpui::Div| {
                        div()
                            .column()
                            .gap(px(theme.space(Space::Sm)))
                            .child(
                                crate::foundation::text(&theme, TypeScale::Label, heading)
                                    .text_tone(&theme, TextTone::Muted),
                            )
                            .child(rows.column().gap(px(theme.space(Space::Sm))))
                    };
                    div()
                        .column()
                        .gap(px(theme.space(Space::Lg)))
                        .child(group(
                            "Outcome",
                            div()
                                .child(
                                    Checkbox::new("scene.drawer.failed")
                                        .label("Failed runs only")
                                        .checked(true)
                                        .on_change(|_, _, _| {}),
                                )
                                .child(
                                    Checkbox::new("scene.drawer.cancelled")
                                        .label("Include cancelled")
                                        .on_change(|_, _, _| {}),
                                ),
                        ))
                        .child(group(
                            "Ownership",
                            div()
                                .child(
                                    Checkbox::new("scene.drawer.mine")
                                        .label("Started by me")
                                        .on_change(|_, _, _| {}),
                                )
                                .child(
                                    Checkbox::new("scene.drawer.watching")
                                        .label("Repositories I watch")
                                        .checked(true)
                                        .on_change(|_, _, _| {}),
                                ),
                        ))
                        .child(group(
                            "Window",
                            div()
                                .child(
                                    Checkbox::new("scene.drawer.today")
                                        .label("Today")
                                        .checked(true)
                                        .on_change(|_, _, _| {}),
                                )
                                .child(
                                    Checkbox::new("scene.drawer.week")
                                        .label("This week")
                                        .on_change(|_, _, _| {}),
                                ),
                        ))
                        .into_any_element()
                })
                // Two controls, sized to their labels and pinned to the
                // reading edge: a full-width primary slab is the loudest
                // thing on the page and says the drawer has one exit.
                .footer(|_, cx| {
                    let theme = cx.theme().clone();
                    div()
                        .row()
                        .justify_end()
                        .gap(px(theme.space(Space::Sm)))
                        .child(
                            Button::new("scene.drawer.cancel")
                                .label("Cancel")
                                .secondary()
                                .on_click(|_, _| {}),
                        )
                        .child(
                            Button::new("scene.drawer.apply")
                                .label("Apply")
                                .primary()
                                .on_click(|_, _| {}),
                        )
                        .into_any_element()
                })
        });
        filters.update(cx, |drawer, cx| {
            drawer.open(window, cx);
            drawer.settle(cx);
        });
        cx.set_global(SceneDrawer { filters });
    }
    let filters = cx.global::<SceneDrawer>().filters.clone();
    let theme = cx.theme().clone();

    stack(&theme)
        .w(px(620.0))
        .h(px(400.0))
        .child(page_behind(&theme, "Runs"))
        .child(filters)
        .into_any_element()
}

pub(super) fn hover_card(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_ordinary(window, cx);
    let card = cx.global::<SceneOrdinary>().hover_card.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(460.0))
        .h(px(300.0))
        .child(caption(&theme, "A preview the pointer can travel into"))
        .child(
            row(&theme)
                .child(crate::foundation::text(
                    &theme,
                    TypeScale::Label,
                    "Reported by",
                ))
                .child(card),
        )
        .into_any_element()
}

pub(super) fn menubar(window: &mut Window, cx: &mut App) -> AnyElement {
    ensure_ordinary(window, cx);
    let bar = cx.global::<SceneOrdinary>().menubar.clone();
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(560.0))
        .h(px(360.0))
        .child(bar)
        .into_any_element()
}

struct SceneBottomSheet(Entity<crate::overlay::sheet::BottomSheet>);
impl Global for SceneBottomSheet {}

pub(super) fn bottom_sheet(window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::overlay::sheet::{BottomSheet, BottomSheetEvent, SheetDetent};
    if !cx.has_global::<SceneBottomSheet>() {
        let sheet = cx.new(|cx| BottomSheet::new("scene.bottom-sheet", window, cx));
        sheet.update(cx, |sheet, cx| {
            sheet.set_title("Choose destination · fixture", cx);
            sheet.set_detents(
                vec![
                    SheetDetent::new("Compact", 290.0),
                    SheetDetent::new("Half", 430.0),
                    SheetDetent::new("Full", 640.0),
                ],
                "Half",
                cx,
            );
            let scroll = gpui::ScrollHandle::new();
            sheet.set_scroll_handle(Some(scroll.clone()), cx);
            sheet.set_content(
                Some(Rc::new(move |_, cx| {
                    let _theme = cx.theme().clone();
                    div()
                        .id("scene.bottom-sheet.list")
                        .column()
                        .size_full()
                        .overflow_y_scroll()
                        .track_scroll(&scroll)
                        .children(
                            [
                                ("home", "Home"),
                                ("work", "Work"),
                                ("library", "Library"),
                                ("airport", "Airport"),
                                ("station", "Station"),
                                ("garden", "Garden"),
                                ("museum", "Museum"),
                            ]
                            .into_iter()
                            .map(|(id, label)| {
                                Button::new(format!("scene.bottom-sheet.destination.{id}"))
                                    .label(label)
                                    .secondary()
                                    .control_size(gpui_kit_theme::ControlSize::Touch)
                                    .on_click(|_, _| {})
                            }),
                        )
                        .into_any_element()
                })),
                cx,
            );
            sheet.open(window, cx);
            sheet.settle(cx);
        });
        cx.subscribe(&sheet, |sheet, event, cx| {
            if let BottomSheetEvent::DetentRequested(id) = event {
                sheet.update(cx, |sheet, cx| {
                    sheet.set_detent(id.clone(), cx);
                });
            }
        })
        .detach();
        cx.set_global(SceneBottomSheet(sheet));
    }
    let theme = cx.theme().clone();
    stack(&theme)
        .w_full()
        .h(px(640.0))
        .child(page_behind(&theme, "Caller-owned destinations"))
        .child(cx.global::<SceneBottomSheet>().0.clone())
        .into_any_element()
}

struct SceneActionSheet(Entity<crate::overlay::sheet::ActionSheet>);
impl Global for SceneActionSheet {}

pub(super) fn action_sheet(window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::overlay::sheet::{ActionSheet, SheetAction, SheetActionState};
    if !cx.has_global::<SceneActionSheet>() {
        let sheet = cx.new(|cx| ActionSheet::new("scene.action-sheet", window, cx));
        sheet.update(cx, |sheet, cx| {
            let mut remove = SheetAction::new("remove", "Remove from collection");
            remove.destructive = true;
            let mut share = SheetAction::new("share", "Share unavailable");
            share.disabled = true;
            sheet.set_actions(
                vec![SheetAction::new("save", "Save a copy"), share, remove],
                cx,
            );
            sheet.set_state(
                SheetActionState::Error(
                    "Fixture refusal: the last attempt failed; nothing was removed.".into(),
                ),
                cx,
            );
            sheet.sheet().update(cx, |sheet, cx| {
                sheet.set_title("Collection actions · fixture", cx);
            });
            sheet.open(window, cx);
            sheet.sheet().update(cx, |sheet, cx| sheet.settle(cx));
        });
        cx.set_global(SceneActionSheet(sheet));
    }
    let theme = cx.theme().clone();
    stack(&theme)
        .w_full()
        .h(px(600.0))
        .child(page_behind(&theme, "Last verified collection"))
        .child(cx.global::<SceneActionSheet>().0.clone())
        .into_any_element()
}

//! Validated conserved flow layouts with persistent node/link labels.
use super::support::*;
use crate::display::plot::{SankeyAlignment, SankeyOrder};

#[derive(Default)]
struct FlowRevisions {
    revision: usize,
    direct: bool,
    selected: Option<SharedString>,
}

pub(super) fn sankey_motion(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let state = crate::motion::keyed::slot::<FlowRevisions>(
        &"scene.sankey.motion-state".into(),
        window.window_handle().window_id(),
        cx,
    );
    let revision = state.borrow().revision % 3;
    let flows = if revision == 0 {
        vec![("ad", "a", "d", 70.0), ("bc", "b", "c", 30.0)]
    } else if revision == 1 {
        vec![("ad", "a", "d", 20.0), ("bc", "b", "c", 80.0)]
    } else {
        vec![("ad", "a", "d", 45.0)]
    };
    let mut nodes = Vec::new();
    let mut links = Vec::new();
    let mut weights = Vec::new();
    for (id, source, target, value) in flows {
        for node in [source, target] {
            nodes.push(SankeyNode::new(
                node,
                node,
                value.to_string(),
                Default::default(),
            ));
        }
        links.push(SankeyLink::new(
            id,
            source,
            target,
            format!("{source} → {target}"),
            value.to_string(),
            Default::default(),
            Default::default(),
            0.0,
        ));
        weights.push(value);
    }
    let (data, _) = SankeyData::new(nodes, links)
        .layout_ordered(
            &weights,
            0.08,
            0.1,
            SankeyAlignment::Left,
            SankeyOrder::Barycenter,
        )
        .expect("conserved synthetic flow");
    let next = state.clone();
    let direct = state.clone();
    let select = state.clone();
    stack(&theme).w(px(600.0)).child(caption(&theme,"Synthetic conserved endpoint layouts · ribbons remain attached during motion; exact current values"))
        .child(div().row().gap_token(&theme,Space::Sm)
            .child(Button::new("scene.sankey.motion.advance").label("Advance flows").on_click(move |window,_|{next.borrow_mut().revision+=1;window.refresh();}))
            .child(Button::new("scene.sankey.motion.direct").label(if state.borrow().direct {"Enable animation"} else {"Direct updates"}).on_click(move |window,_|{let mut s=direct.borrow_mut();s.direct = !s.direct;window.refresh();})))
        .child(SankeyChart::new("scene.sankey.motion","Keyed node and ribbon updates",PlotState::Ready(data)).animate(!state.borrow().direct).labels(true)
            .selected(state.borrow().selected.clone()).on_current(move |id,window,_|{select.borrow_mut().selected=Some(id);window.refresh();}))
        .into_any_element()
}

pub(super) fn sankey_layout(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let graph = SankeyData::new(
        [
            ("a", "Incoming A", "70"),
            ("b", "Incoming B", "30"),
            ("c", "Destination C", "30"),
            ("d", "Destination D", "70"),
        ]
        .map(|(id, label, value)| SankeyNode::new(id, label, value, Default::default())),
        [
            ("ad", "a", "d", "A → D", "70"),
            ("bc", "b", "c", "B → C", "30"),
        ]
        .map(|(id, source, target, label, value)| {
            SankeyLink::new(
                id,
                source,
                target,
                label,
                value,
                Default::default(),
                Default::default(),
                0.0,
            )
        }),
    );
    stack(&theme)
        .w(px(860.0))
        .child(caption(
            &theme,
            "Synthetic conserved flows · same 70:30 scale · deterministic crossing reduction",
        ))
        .child(
            div().row().gap_token(&theme, Space::Lg).children(
                [
                    (SankeyOrder::Input, "input", "Input order"),
                    (SankeyOrder::Barycenter, "ordered", "Barycenter order"),
                ]
                .map(|(order, id, label)| {
                    let (data, _) = graph
                        .clone()
                        .layout_ordered(&[70.0, 30.0], 0.08, 0.1, SankeyAlignment::Left, order)
                        .expect("valid flows");
                    div().w(px(380.0)).child(
                        SankeyChart::new(
                            Ident::new("scene.sankey").child(id),
                            label,
                            PlotState::Ready(data),
                        )
                        .labels(true)
                        .on_current(|_, _, _| {}),
                    )
                }),
            ),
        )
        .into_any_element()
}

pub(super) fn raw_candlestick(_window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::display::chart::{
        cartesian::CartesianChart,
        data::{ChartScale, ChartValue, RawPoint, RawSeries, SeriesMark, ValueAxis},
        scale::{NumericScale, ScaleKind},
    };
    use crate::display::plot::RawOhlc;
    let theme = cx.theme().clone();
    let day = 86_400_000.0;
    let x = ChartScale::Numeric(
        NumericScale::new(ScaleKind::Time, [0.0, 4.0 * day]).expect("time domain"),
    );
    let candles = RawOhlc::series(
        "ohlc",
        "price",
        [
            RawOhlc::new(
                "day-a",
                day,
                [30.0, 60.0, 20.0, 50.0],
                "Day A",
                "O30 H60 L20 C50",
            ),
            RawOhlc::new(
                "day-b",
                day * 2.0,
                [65.0, 75.0, 35.0, 40.0],
                "Day B",
                "O65 H75 L35 C40",
            ),
            RawOhlc::new(
                "day-c",
                day * 3.0,
                [45.0, 55.0, 30.0, 45.0],
                "Day C · doji",
                "O45 H55 L30 C45",
            ),
        ],
        theme.colors.success,
        theme.colors.danger,
    )
    .expect("validated OHLC");
    let volume = RawSeries::new("volume", "volume", SeriesMark::Bar)
        .tint(theme.colors.accent.opacity(0.22))
        .points(
            [
                ("day-a", day, 120.0),
                ("day-b", 2.0 * day, 260.0),
                ("day-c", 3.0 * day, 80.0),
            ]
            .map(|(id, x, value)| RawPoint::new(id, ChartValue::Number(x), Some(value))),
        );
    let overlay = RawSeries::new("reference", "price", SeriesMark::Line)
        .tint(theme.colors.warning)
        .points(
            [
                ("day-a", day, 42.0),
                ("day-b", 2.0 * day, 48.0),
                ("day-c", 3.0 * day, 46.0),
            ]
            .map(|(id, x, value)| RawPoint::new(id, ChartValue::Number(x), Some(value))),
        );
    stack(&theme).w(px(900.0)).child(caption(&theme, "Synthetic OHLC · shared UTC-duration x scale · independent volume axis · caller overlay"))
        .child(CartesianChart::new("scene.raw-ohlc", "OHLC with volume and overlay", x, [
            ValueAxis { id: "price".into(), label: "Price".into(), scale: NumericScale::new(ScaleKind::Linear, [0.0, 80.0]).expect("price domain") },
            ValueAxis { id: "volume".into(), label: "Volume".into(), scale: NumericScale::new(ScaleKind::Linear, [0.0, 300.0]).expect("volume domain") },
        ]).series([volume, candles, overlay]).format_ticks(move |axis, value| if axis == "x" { format!("D{:.0}", value / day).into() } else { value.to_string().into() }).selected(Some(ChartSelection::new("ohlc", "day-b"))).on_event(|_, _, _| {}))
        .into_any_element()
}

//! Raw-value specialized visualization exhibits, using original synthetic data.

use super::support::*;
use crate::display::specialized::{
    HierarchyNode, SpecializedChart, SpecializedData, WeightedValue,
};

#[derive(Default)]
struct Exploration {
    revision: usize,
    focus: Option<SharedString>,
    selected: Option<SharedString>,
    refuse: bool,
}

pub(super) fn specialized_exploration(window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let state = crate::motion::keyed::slot::<Exploration>(
        &"scene.specialized.explore-state".into(),
        window.window_handle().window_id(),
        cx,
    );
    let revision = state.borrow().revision;
    let selected = state.borrow().selected.clone();
    let focus = state
        .borrow()
        .focus
        .clone()
        .unwrap_or_else(|| "total".into());
    let root = HierarchyNode::branch(
        "total",
        "All samples",
        vec![
            HierarchyNode::branch(
                "group",
                "Processing",
                vec![
                    HierarchyNode::leaf(WeightedValue::new(
                        "parse",
                        "Parsing",
                        if revision.is_multiple_of(2) {
                            12.0
                        } else {
                            47.0
                        },
                    )),
                    HierarchyNode::leaf(WeightedValue::new(
                        "execute",
                        "Execution",
                        if revision.is_multiple_of(2) {
                            68.0
                        } else {
                            13.0
                        },
                    )),
                ],
            ),
            HierarchyNode::leaf(WeightedValue::new("deliver", "Delivery", 20.0)),
        ],
    );
    let next = state.clone();
    let refusal = state.clone();
    let charts = [
        (
            "tree",
            "Area · drill/back",
            SpecializedData::treemap_at(&root, &focus),
        ),
        (
            "sun",
            "Angle · drill/back",
            SpecializedData::sunburst_at(&root, &focus),
        ),
        (
            "funnel",
            "Width · enter/update/exit",
            SpecializedData::funnel(match revision % 3 {
                0 => vec![
                    WeightedValue::new("start", "Started", 120.0),
                    WeightedValue::new("finish", "Finished", 24.0),
                ],
                1 => vec![
                    WeightedValue::new("start", "Started", 75.0),
                    WeightedValue::new("new", "New sample", 38.0),
                ],
                _ => vec![],
            }),
        ),
    ];
    stack(&theme).w(px(860.0))
        .child(caption(&theme,"Synthetic live fixtures · advance during motion to interrupt; empty exit shapes are not selectable"))
        .child(div().row().gap_token(&theme,Space::Sm)
            .child(Button::new("scene.specialized.advance").label("Advance samples").on_click(move |window,_| { next.borrow_mut().revision+=1; window.refresh(); }))
            .child(Button::new("scene.specialized.refuse").label(if state.borrow().refuse { "Accept proposals" } else { "Refuse proposals" }).on_click(move |window,_| { let mut s=refusal.borrow_mut(); s.refuse = !s.refuse; window.refresh(); })))
        .child(div().row().gap_token(&theme,Space::Lg).children(charts.into_iter().map(|(id,label,data)| {
            let select = state.clone();
            let navigate = state.clone();
            div().w(px(260.0)).child(SpecializedChart::new(Ident::new("scene.specialized.live").child(id),label,PlotState::Ready(data.expect("valid exploration fixture")))
                .motion(true).labels(true).selected(selected.clone())
                .on_current(move |id,window,_| { let mut s=select.borrow_mut(); if !s.refuse { s.selected=Some(id); } window.refresh(); })
                .on_navigate(move |id,window,_| { let mut s=navigate.borrow_mut(); if !s.refuse { s.focus=Some(id); } window.refresh(); }))
        }))).into_any_element()
}

pub(super) fn specialized(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let leaf = |id, label, value| HierarchyNode::leaf(WeightedValue::new(id, label, value));
    let root = HierarchyNode::branch(
        "total",
        "All work",
        vec![
            leaf("build", "Build", 60.0),
            HierarchyNode::branch(
                "verify",
                "Verification",
                vec![leaf("tests", "Tests", 30.0), leaf("lint", "Lint", 10.0)],
            ),
        ],
    );
    let fixtures = [
        (
            "treemap",
            "Treemap · area = work",
            SpecializedData::treemap(&root),
        ),
        (
            "sunburst",
            "Sunburst · angle = work",
            SpecializedData::sunburst(&root),
        ),
        (
            "funnel",
            "Funnel · width = count",
            SpecializedData::funnel(vec![
                WeightedValue::new("seen", "Seen", 120.0),
                WeightedValue::new("started", "Started", 75.0),
                WeightedValue::new("done", "Completed", 24.0),
            ]),
        ),
    ];
    stack(&theme)
        .w(px(940.0))
        .child(caption(
            &theme,
            "Synthetic hierarchy fixtures · select marks with arrow keys or value keys",
        ))
        .child(
            div()
                .row()
                .gap_token(&theme, Space::Lg)
                .children(fixtures.into_iter().map(|(id, label, data)| {
                    div().w(px(280.0)).child(
                        SpecializedChart::new(
                            Ident::new("scene.specialized").child(id),
                            label,
                            PlotState::Ready(data.expect("valid hierarchy")),
                        )
                        .on_current(|_, _, _| {}),
                    )
                })),
        )
        .child(
            div().w(px(220.0)).child(SpecializedChart::new(
                "scene.specialized.zero",
                "Zero is measured, not missing",
                PlotState::Stale {
                    data: SpecializedData::funnel(vec![WeightedValue::new(
                        "zero",
                        "Measured zero",
                        0.0,
                    )])
                    .expect("zero"),
                    reason: "Refresh refused; verified zero retained".into(),
                },
            )),
        )
        .into_any_element()
}

pub(super) fn specialized_distribution(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let fixtures = [
        (
            "histogram",
            "Histogram · count per bin",
            SpecializedData::histogram(&[-2.0, -1.0, 0.0, 0.0, 1.0, 3.0], &[-2.0, 0.0, 1.0, 3.0]),
        ),
        (
            "box",
            "Box · R-7 / Tukey · domain 0–110",
            SpecializedData::box_plot(
                "latency",
                "Latency",
                &[1.0, 2.0, 4.0, 7.0, 8.0, 100.0],
                [0.0, 110.0],
            ),
        ),
        (
            "range",
            "Range · domain −10–30",
            SpecializedData::range("interval", "Expected interval", -4.0, 18.0, [-10.0, 30.0]),
        ),
        (
            "waterfall",
            "Waterfall · signed changes",
            SpecializedData::waterfall(vec![
                WeightedValue::new("open", "Opening", 60.0),
                WeightedValue::new("cost", "Cost", -90.0),
                WeightedValue::new("credit", "Credit", 10.0),
            ]),
        ),
    ];
    stack(&theme)
        .w(px(1000.0))
        .child(caption(
            &theme,
            "Synthetic distribution fixtures · explicit bin edges and R-7 / Tukey statistics",
        ))
        .child(
            div()
                .row()
                .flex_wrap()
                .gap_token(&theme, Space::Lg)
                .children(fixtures.into_iter().map(|(id, label, data)| {
                    div().w(px(440.0)).child(
                        SpecializedChart::new(
                            Ident::new("scene.specialized").child(id),
                            label,
                            PlotState::Ready(data.expect("valid synthetic fixture")),
                        )
                        .on_current(|_, _, _| {}),
                    )
                })),
        )
        .into_any_element()
}

//! A schema, rendered as data or as a form.

use super::support::*;

/// A document with the three facts a viewer usually confuses: a key holding
/// `null`, a key holding an empty object, and a key that is simply not here.
pub(super) fn scene_document() -> JsonValue {
    JsonValue::object([
        ("id", JsonValue::string("run-4812")),
        ("attempts", JsonValue::number("3")),
        ("streaming", JsonValue::Bool(true)),
        // Nothing has been recorded here yet, which is not the same as the
        // key being missing: `resumed_from` is missing, and says nothing.
        ("cursor", JsonValue::Null),
        ("labels", JsonValue::object(Vec::<(&str, JsonValue)>::new())),
        (
            "credentials",
            JsonValue::object([("token", JsonValue::redacted("51 characters"))]),
        ),
        (
            "request",
            JsonValue::object([
                ("method", JsonValue::string("POST")),
                (
                    "headers",
                    JsonValue::object([
                        ("content-type", JsonValue::string("application/json")),
                        ("authorization", JsonValue::redacted("a value")),
                    ]),
                ),
            ]),
        ),
        (
            "steps",
            JsonValue::array([
                JsonValue::string("plan"),
                JsonValue::string("apply"),
                JsonValue::object([("retries", JsonValue::number("0"))]),
            ]),
        ),
    ])
}

pub(super) fn json_view(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(520.0))
        .child(
            JsonView::new("scene.json", scene_document())
                .expanded_paths(&["credentials", "request", "request/headers", "steps"])
                .selected("request/method")
                .on_toggle(|_, _, _, _| {})
                .on_select(|_, _, _| {}),
        )
        .into_any_element()
}

/// The form kept across frames.
///
/// Its fields own carets, open menus and a selection, so rebuilding it every
/// frame would throw away whatever had been typed into it.
pub(super) struct SceneSchemaForm {
    form: Entity<SchemaForm>,
}

impl Global for SceneSchemaForm {}

pub(super) fn scene_schema() -> Schema {
    Schema::new()
        .field(
            SchemaField::new(
                "path",
                SchemaKind::Text {
                    placeholder: Some("relative to the workspace".into()),
                    secret: false,
                },
            )
            .label("File")
            .description("Which file the call reads")
            .required(true),
        )
        .field(
            SchemaField::new(
                "max_bytes",
                SchemaKind::Integer(NumberBounds::new().min(1.0).max(65_536.0).step(1024.0)),
            )
            .label("Maximum bytes"),
        )
        .field(
            SchemaField::new("follow_symlinks", SchemaKind::Boolean).label("Follow symbolic links"),
        )
        .field(
            SchemaField::new(
                "encoding",
                SchemaKind::Enum(vec![
                    SchemaChoice::new("utf-8", "UTF-8"),
                    SchemaChoice::new("latin-1", "Latin-1"),
                ]),
            )
            .label("Encoding")
            .required(true),
        )
        .field(
            SchemaField::new("attachments", SchemaKind::Files { max: Some(2) })
                .label("Attachments")
                .description("Paths are acquired and accepted by the host"),
        )
        .field(
            SchemaField::new(
                "headers",
                SchemaKind::List {
                    item: Box::new(SchemaField::new(
                        "header",
                        SchemaKind::Object(vec![
                            SchemaField::new(
                                "name",
                                SchemaKind::Text {
                                    placeholder: Some("Header name".into()),
                                    secret: false,
                                },
                            )
                            .label("Name")
                            .required(true),
                            SchemaField::new("enabled", SchemaKind::Boolean).label("Enabled"),
                        ]),
                    )),
                    max: Some(3),
                },
            )
            .label("Headers"),
        )
        .field(
            SchemaField::new(
                "limits",
                SchemaKind::Object(vec![
                    SchemaField::new("timeout_ms", SchemaKind::Integer(NumberBounds::new()))
                        .label("Timeout in milliseconds"),
                ]),
            )
            .label("Limits"),
        )
        .field(
            SchemaField::new(
                "matcher",
                SchemaKind::Unrenderable(
                    "This argument is one of three shapes at once, and no single control \
                     stands for that."
                        .into(),
                ),
            )
            .label("Matcher")
            .required(true),
        )
}

pub(super) fn schema_form(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneSchemaForm>() {
        set_schema_file_policy(DefaultSchemaFilePolicy, cx);
        let form = cx.new(|cx| SchemaForm::new("scene.schema", scene_schema(), window, cx));
        form.update(cx, |form, cx| {
            // An error the host returned rather than one the form derived:
            // only the host knows this path is outside the workspace.
            form.set_error("path", "That path is outside the workspace.", cx);
            let _ = form.set_field_validation("encoding", ValidationState::Validating, cx);
            form.set_files("attachments", vec!["/workspace/brief.md".into()], cx)
                .expect("the scene's permissive file policy accepts its fixture");
            form.add_list_item("headers", window, cx);
        });
        cx.set_global(SceneSchemaForm { form });
    }
    let form = cx.global::<SceneSchemaForm>().form.clone();
    let theme = cx.theme().clone();
    stack(&theme).w(px(520.0)).child(form).into_any_element()
}

//! What more than one family of scenes needs.
//!
//! Everything here is used by scenes in at least two families. A fixture
//! that only one family builds lives in that family, next to the scenes
//! that read it, so that moving a component moves its fixture with it.

pub(super) use gpui::{
    AnyElement, App, Corners, DevicePixels, Entity, Focusable, Global, IntoElement, Pixels,
    RenderImage, SharedString, SpriteBlendMode, SpriteColorMode, SpriteInstance, SpriteTransform,
    Window, bounds, canvas, conic_gradient_stops, div, linear_color_stop, point, prelude::*, px,
    radial_gradient_stops, radians, size,
};
pub(super) use gpui_kit_assets::Icon;
pub(super) use gpui_kit_semantics::{NodeSpec, Role, Semantic};
pub(super) use gpui_kit_theme::{Radius, Space, Surface, SyntaxColor, TextTone, Theme, TypeScale};
pub(super) use std::rc::Rc;
pub(super) use std::sync::{Arc, OnceLock};
pub(super) use std::time::Duration;

pub(super) use crate::controls::combobox::Combobox;
pub(super) use crate::controls::input::TextInput;
pub(super) use crate::controls::keybinding_recorder::KeybindingRecorder;
pub(super) use crate::controls::number_input::NumberInput;
pub(super) use crate::controls::select::{Select, SelectOption};
pub(super) use crate::controls::split_button::SplitButton;
pub(super) use crate::controls::tag_input::TagInput;
pub(super) use crate::controls::textarea::{Frame, TextArea};
pub(super) use crate::display::badge::Tone;
pub(super) use crate::display::icon::{Icon as IconView, IconTone};
pub(super) use crate::foundation::ActiveTheme;
pub(super) use crate::foundation::direction::{ActiveDirection, DirectionalExt};
pub(super) use crate::interaction::dnd;
pub(super) use crate::overlay::toast::push as toast_push;
pub(super) use crate::overlay::{Edge, Kbd, Overlay, Placement, Tooltip, Tooltipped};
pub(super) use crate::prelude::*;
pub(super) use crate::strings::{ActiveStrings, StringKey};

pub(super) fn stack(theme: &Theme) -> gpui::Div {
    div()
        .column()
        .gap(px(theme.spacing.md))
        .p(px(theme.spacing.lg))
        .page(theme)
        .font_family(theme.typography.sans.clone())
}

pub(super) fn row(theme: &Theme) -> gpui::Div {
    div()
        .row()
        .flex_wrap()
        .gap(px(theme.spacing.sm))
        .items_center()
}

/// A pane of fixture copy, for a scene that needs something on both sides of a
/// divider or below a viewport.
pub(super) fn filler(theme: &Theme, title: &'static str, lines: usize) -> gpui::Div {
    let mut pane = div()
        .flex()
        .flex_col()
        .gap(px(theme.space(Space::Xs)))
        .p(px(theme.space(Space::Md)))
        .child(crate::foundation::text(
            theme,
            TypeScale::Label,
            SharedString::from(title),
        ));
    for line in 1..=lines {
        pane = pane.child(
            crate::foundation::text(
                theme,
                TypeScale::Code,
                SharedString::from(format!("Fixture line {line:02}")),
            )
            .text_tone(theme, TextTone::Muted),
        );
    }
    pane
}

/// A caption naming the state a drag scene was staged in.
pub(super) fn caption(theme: &Theme, text: impl Into<SharedString>) -> gpui::Div {
    crate::foundation::text(theme, TypeScale::Caption, text.into())
        .text_tone(theme, TextTone::Muted)
}

/// The element a host hands back for an image this crate did not fetch.
///
/// A flat fill rather than a picture, because the crate ships no photographs
/// and a scene has to photograph the same pixels every run. Neutral, and
/// quieter than the surface it sits in: a stand-in painted in the accent was
/// the loudest thing in every scene that used one, which said the placeholder
/// mattered more than the component being reviewed around it.
pub(super) fn scene_picture(label: &'static str, cx: &App) -> AnyElement {
    let theme = cx.theme().clone();
    div()
        .size_full()
        .flex()
        .flex_col()
        .items_center()
        .justify_center()
        .gap(px(theme.space(gpui_kit_theme::Space::Xs)))
        .bg(theme.colors.sunken)
        .child(
            gpui_kit_assets::icon(gpui_kit_assets::Icon::Image)
                .size(px(theme.typography.subtitle.line_height))
                .text_color(theme.colors.text_faint),
        )
        .child(
            crate::foundation::text(&theme, TypeScale::Caption, SharedString::new_static(label))
                .text_tone(&theme, TextTone::Faint),
        )
        .into_any_element()
}

/// The ordinary-application views that own state across frames.
pub(super) struct SceneOrdinary {
    pub(super) menubar: Entity<Menubar>,
    pub(super) hover_card: Entity<HoverCard>,
    pub(super) copy_idle: Entity<CopyButton>,
    pub(super) copy: Entity<CopyButton>,
    pub(super) copy_refused: Entity<CopyButton>,
}

impl Global for SceneOrdinary {}

pub(super) fn ensure_ordinary(window: &mut Window, cx: &mut App) {
    if cx.has_global::<SceneOrdinary>() {
        return;
    }
    let menubar = cx.new(|cx| {
        Menubar::new(
            "scene.menubar",
            [
                MenubarMenu::new(
                    "file",
                    "File",
                    [
                        MenuItem::command("file.new", "New run").shortcut("cmd-n"),
                        MenuItem::command("file.open", "Open workspace").shortcut("cmd-o"),
                        MenuItem::separator("file.rule"),
                        MenuItem::submenu(
                            "file.export",
                            "Export",
                            [
                                MenuItem::command("file.export.json", "As JSON"),
                                MenuItem::command("file.export.text", "As plain text"),
                            ],
                        ),
                    ],
                ),
                MenubarMenu::new(
                    "edit",
                    "Edit",
                    [
                        MenuItem::command("edit.undo", "Undo").shortcut("cmd-z"),
                        MenuItem::check("edit.wrap", "Wrap lines", true),
                    ],
                ),
                MenubarMenu::new("view", "View", [MenuItem::command("view.zoom", "Zoom in")]),
                MenubarMenu::new("policy", "Policy", []).disabled(true),
            ],
            window,
            cx,
        )
    });
    menubar.update(cx, |bar, cx| bar.open("file", window, cx));

    let hover_card = cx.new(|cx| {
        HoverCard::new("scene.hover-card", window, cx)
            .name("Run 4821")
            .trigger(|_, cx| {
                let theme = cx.theme().clone();
                crate::foundation::text(&theme, TypeScale::Caption, "run 4821")
                    .text_color(theme.colors.accent)
                    .into_any_element()
            })
            .content(|_, cx| {
                let theme = cx.theme().clone();
                div()
                    .column()
                    // A column stretches its children across, and a badge
                    // stretched that far stops reading as a label and starts
                    // reading as a filled bar.
                    .items_start()
                    .gap(px(theme.spacing.xs))
                    .child(crate::foundation::text(
                        &theme,
                        TypeScale::Strong,
                        "Nightly regression sweep",
                    ))
                    .child(
                        crate::foundation::text(
                            &theme,
                            TypeScale::Caption,
                            "Finished in 4 minutes, 12 checks, none failed.",
                        )
                        .text_tone(&theme, TextTone::Muted),
                    )
                    .child(Badge::new("Ready").success())
                    .into_any_element()
            })
    });
    hover_card.update(cx, |card, cx| card.open(cx));

    // Nobody has pressed this one. It is the state a reader sees almost all
    // of the time, and it was the one state of the three no image held.
    let copy_idle = cx.new(|cx| {
        CopyButton::new("scene.copy-idle", window, cx)
            .text("run-4821-9f3a")
            .copier(|_, _| Ok(()))
    });

    // The confirmation outlives the capture. At its ordinary length it expires
    // partway through a run, so which of the two states this scene showed
    // depended on how long the run took to reach it.
    let copy = cx.new(|cx| {
        CopyButton::new("scene.copy", window, cx)
            .text("run-4821-9f3a")
            .confirmation(std::time::Duration::from_secs(60 * 60))
            .copier(|_, _| Ok(()))
    });
    copy.update(cx, |button, cx| button.copy(cx));

    let copy_refused = cx.new(|cx| {
        CopyButton::new("scene.copy-refused", window, cx)
            .text("run-4821-9f3a")
            .copier(|_, _| Err("The clipboard did not take it.".into()))
    });
    copy_refused.update(cx, |button, cx| button.copy(cx));

    cx.set_global(SceneOrdinary {
        menubar,
        hover_card,
        copy_idle,
        copy,
        copy_refused,
    });
}

Source: crates/gpui-kit/src/display/geography/view.rs