atproto Thingiverse but good
10

Configure Feed

Select the types of activity you want to include in your feed.

de-slop the search page somewhat

Orual (Jun 29, 2026, 7:06 PM EDT) da5cf4aa 919dcb93

+90 -73
-1
assets/styling/home.css
··· 56 56 57 57 .hero-band-pitch { 58 58 margin: 0; 59 - max-width: 60ch; 60 59 color: var(--color-text); 61 60 font-size: clamp(1rem, 2vw, 1.15rem); 62 61 line-height: 1.45;
+24 -8
assets/styling/placeholders.css
··· 9 9 max-width: 1180px; 10 10 margin: 0 auto; 11 11 padding: clamp(0.75rem, 2vw, 1.75rem) clamp(1rem, 3vw, 2.5rem); 12 - display: flex; 13 - flex-direction: column; 14 - gap: 0.9rem; 12 + display: grid; 13 + grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr); 14 + align-items: start; 15 + gap: 1rem; 15 16 } 16 17 17 18 .search-intro { 18 19 display: flex; 19 20 flex-direction: column; 20 - gap: 0.75rem; 21 - padding: clamp(1.25rem, 3vw, 2rem); 21 + gap: 0.5rem; 22 + padding: 1rem; 22 23 } 23 24 24 25 .search-intro h1 { 25 26 margin: 0; 26 27 color: var(--color-emphasis); 27 28 letter-spacing: -0.03em; 29 + font-size: 1.75rem; 30 + line-height: 1; 28 31 } 29 32 30 - .search-note { 33 + .search-query { 31 34 margin: 0; 32 - color: var(--color-muted); 33 - font-size: 0.9rem; 35 + color: var(--color-subtle); 36 + font-family: var(--font-mono); 37 + font-size: 0.85rem; 38 + overflow-wrap: anywhere; 39 + } 40 + 41 + .search-content { 42 + min-width: 0; 43 + display: flex; 44 + flex-direction: column; 45 + gap: 1rem; 34 46 } 35 47 36 48 .search-results-grid { ··· 59 71 } 60 72 61 73 @media (max-width: 820px) { 74 + .search-page { 75 + grid-template-columns: 1fr; 76 + } 77 + 62 78 .search-results-grid { 63 79 grid-template-columns: 1fr; 64 80 }
-2
src/about.rs
··· 1 1 use dioxus::prelude::*; 2 2 3 - use crate::Route; 4 - 5 3 #[component] 6 4 pub(crate) fn About() -> Element { 7 5 rsx! {
+52 -60
src/search.rs
··· 126 126 results.read().as_ref(), 127 127 ); 128 128 129 - let page_note = if route_query.is_empty() { 130 - "Catalog search uses the local appview index. Start from the header search field." 131 - } else { 132 - "Catalog search uses the local appview index and returns projects from the Polymodel library." 133 - }; 134 - 135 129 rsx! { 136 130 main { class: "search-page", 137 131 section { class: "search-intro blueprint-panel", aria_label: "Search", 138 132 span { class: "status-pill status-muted", "Catalog search" } 139 - h1 { "Search" } 140 - if route_query.is_empty() { 141 - p { class: "product-lede", "Search the Polymodel catalog by project name, tags, authors, and indexed description text." } 142 - } else { 143 - p { class: "product-lede", "Results for \"{route_query}\"" } 133 + if !route_query.is_empty() { 134 + p { class: "search-query", "\"{route_query}\"" } 144 135 } 145 - p { class: "search-note", "{page_note}" } 146 136 } 147 137 148 - match state { 149 - SearchState::Idle => rsx! { 150 - section { class: "state-card empty-state search-state-card", aria_label: "Search instructions", 151 - span { class: "status-pill status-muted", "Ready" } 152 - h2 { "Run a catalog search" } 153 - p { "Use the header search field to look up printable projects in the indexed Polymodel catalog." } 154 - Link { class: "button button-secondary", to: Route::Browse {}, "Browse instead" } 155 - } 156 - }, 157 - SearchState::Loading => rsx! { 158 - section { class: "search-results-grid", aria_label: "Search results loading", 159 - for index in 0..6 { 160 - div { key: "search-skeleton-{index}", class: "state-card loading-state", 161 - span { class: "status-pill", "Searching" } 162 - div { class: "skeleton-line skeleton-wide" } 163 - div { class: "skeleton-line" } 164 - div { class: "skeleton-box" } 138 + div { class: "search-content", 139 + match state { 140 + SearchState::Idle => rsx! { 141 + section { class: "state-card empty-state search-state-card", aria_label: "Search instructions", 142 + span { class: "status-pill status-muted", "Ready" } 143 + h2 { "Search from the header" } 144 + p { "Find indexed projects by name, tag, author, or description." } 145 + Link { class: "button button-secondary", to: Route::Browse {}, "Browse instead" } 146 + } 147 + }, 148 + SearchState::Loading => rsx! { 149 + section { class: "search-results-grid", aria_label: "Search results loading", 150 + for index in 0..6 { 151 + div { key: "search-skeleton-{index}", class: "state-card loading-state", 152 + span { class: "status-pill", "Searching" } 153 + div { class: "skeleton-line skeleton-wide" } 154 + div { class: "skeleton-line" } 155 + div { class: "skeleton-box" } 156 + } 165 157 } 166 158 } 167 - } 168 - }, 169 - SearchState::Error(error) => rsx! { 170 - section { class: "state-card error-state search-state-card", aria_label: "Search error", 171 - h2 { "Search failed" } 172 - p { "{error}" } 173 - button { class: "button button-secondary", onclick: move |_| search.restart(), "Retry" } 174 - } 175 - }, 176 - SearchState::Empty => rsx! { 177 - section { class: "state-card empty-state search-state-card", aria_label: "No search results", 178 - span { class: "status-pill status-muted", "No matches" } 179 - h2 { "No projects match \"{route_query}\"" } 180 - p { "Try a broader term, a material, or a project category. The local index answered; it just came back empty." } 181 - Link { class: "button button-secondary", to: Route::Browse {}, "Back to browse" } 182 - } 183 - }, 184 - SearchState::Populated(search_results) => rsx! { 185 - section { class: "search-results-grid", aria_label: "Search results", 186 - for thing in search_results.items.clone() { 187 - SearchThingCard { key: "{thing.uri}", thing } 159 + }, 160 + SearchState::Error(error) => rsx! { 161 + section { class: "state-card error-state search-state-card", aria_label: "Search error", 162 + h2 { "Search failed" } 163 + p { "{error}" } 164 + button { class: "button button-secondary", onclick: move |_| search.restart(), "Retry" } 188 165 } 189 - } 190 - {search_pagination( 191 - client.clone(), 192 - route_query.clone(), 193 - results, 194 - search_results.clone(), 195 - search_results.cursor.clone(), 196 - search_results.load_more.clone(), 197 - )} 198 - }, 166 + }, 167 + SearchState::Empty => rsx! { 168 + section { class: "state-card empty-state search-state-card", aria_label: "No search results", 169 + span { class: "status-pill status-muted", "No matches" } 170 + h2 { "No projects match \"{route_query}\"" } 171 + p { "Try a broader term, material, or category." } 172 + Link { class: "button button-secondary", to: Route::Browse {}, "Back to browse" } 173 + } 174 + }, 175 + SearchState::Populated(search_results) => rsx! { 176 + section { class: "search-results-grid", aria_label: "Search results", 177 + for thing in search_results.items.clone() { 178 + SearchThingCard { key: "{thing.uri}", thing } 179 + } 180 + } 181 + {search_pagination( 182 + client.clone(), 183 + route_query.clone(), 184 + results, 185 + search_results.clone(), 186 + search_results.cursor.clone(), 187 + search_results.load_more.clone(), 188 + )} 189 + }, 190 + } 199 191 } 200 192 } 201 193 }
+14 -1
src/shell.rs
··· 59 59 /// `SessionControl`, which issues a real GET to `/oauth/start`). 60 60 #[component] 61 61 fn ShellSearch() -> Element { 62 - let mut query = use_signal(String::new); 62 + let route = use_route::<Route>(); 63 + let route_query = match route { 64 + Route::Search { q } => Some(q), 65 + _ => None, 66 + }; 67 + let mut query = use_signal(|| route_query.clone().unwrap_or_default()); 68 + let mut synced_route_query = use_signal(|| route_query.clone()); 69 + 70 + if *synced_route_query.read() != route_query { 71 + synced_route_query.set(route_query.clone()); 72 + if let Some(route_query) = route_query { 73 + query.set(route_query); 74 + } 75 + } 63 76 64 77 rsx! { 65 78 div { class: "app-search", role: "search",
-1
src/thing_card.rs
··· 143 143 img { src: "{media.url}", alt: "{media.alt}" } 144 144 } else { 145 145 div { class: "thing-card-placeholder blueprint-media", role: "img", aria_label: "Blueprint placeholder for missing preview media", 146 - span { class: "status-pill status-muted", "No preview" } 147 146 span { class: "blueprint-axis blueprint-axis-x", "X" } 148 147 span { class: "blueprint-axis blueprint-axis-y", "Y" } 149 148 span { class: "blueprint-dimension", "preview pending" }