diff --git a/src/components/admin.rs b/src/components/admin.rs index c3ce63f6..9c182965 100644 --- a/src/components/admin.rs +++ b/src/components/admin.rs @@ -169,7 +169,7 @@ pub fn AdminApp(node: NodeWithChildren) -> Element { let sel = console_tab(tab(), wide); let agenda_pane = rsx! { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar", {icon_el("app/program")} } h3 { class: "title-medium", "{t(\"console.agenda\")}" } @@ -231,7 +231,7 @@ pub fn AdminApp(node: NodeWithChildren) -> Element { // something is projected; when the item has no sections, the card stays // (with a note) rather than silently vanishing. if can_manage && active_id.is_some() { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar", span { class: "material-icons", "center_focus_strong" } } h3 { class: "title-medium", "{t(\"console.focusSection\")}" } @@ -333,7 +333,7 @@ pub fn AdminApp(node: NodeWithChildren) -> Element { } // ── Polls ─────────────────────────────────────────────────────── if sel == 2 { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar", {icon_el("vote/poll")} } h3 { class: "title-medium", "{t(\"admin.results\")}" } @@ -360,7 +360,7 @@ pub fn AdminApp(node: NodeWithChildren) -> Element { // What has landed in this context while the meeting ran: an amendment // posted from the floor shows up here without the chair going looking. if sel == 3 { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "view_agenda" } } h3 { class: "title-medium", "{t(\"layout.feed\")}" } diff --git a/src/components/editor.rs b/src/components/editor.rs index ae97c46d..a4233a07 100644 --- a/src/components/editor.rs +++ b/src/components/editor.rs @@ -816,7 +816,7 @@ pub fn EditorApp(node: NodeWithChildren) -> Element { if !is_auth { // DESIGN: an expressive locked-barrier state instead of a plain card. return rsx! { - div { class: "card", + div { class: "card app-card", div { class: "empty-state empty-state-sm", div { class: "empty-state-orb empty-state-orb-sm", span { class: "material-icons", "lock" } @@ -831,7 +831,7 @@ pub fn EditorApp(node: NodeWithChildren) -> Element { let cmd_seed = initial_html.clone(); rsx! { - div { class: "card", + div { class: "card app-card", div { class: "card-content", // Title field. maxlength caps the node name length (#111). div { class: "text-field mb-2", diff --git a/src/components/feedback_app.rs b/src/components/feedback_app.rs index 3d6388f7..f7afd18f 100644 --- a/src/components/feedback_app.rs +++ b/src/components/feedback_app.rs @@ -210,7 +210,7 @@ pub fn FeedbackApp() -> Element { if !is_auth { return rsx! { - div { class: "card", + div { class: "card app-card", div { class: "empty-state empty-state-sm", div { class: "empty-state-orb empty-state-orb-sm", span { class: "material-icons", "lock" } @@ -235,7 +235,7 @@ pub fn FeedbackApp() -> Element { }; rsx! { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", {super::loader::feedback_icon_el()} } h3 { class: "title-medium", diff --git a/src/components/file.rs b/src/components/file.rs index 8ff80367..df25de36 100644 --- a/src/components/file.rs +++ b/src/components/file.rs @@ -258,18 +258,21 @@ fn is_presentation(mime: &str) -> bool { /// /// A different question from [`OfficeViewer`], which is about which THIRD PARTY /// sees the document. A PDF already goes to nobody: every browser has a viewer -/// built in and it renders locally. So this is fidelity against readability, and -/// the browser keeps the default, because it is exact, it prints, and it is what -/// a reader already expects a PDF to look like. +/// built in and it renders locally. So this is fidelity against readability. /// -/// The native one is for the case the browser's viewer is bad at: a long -/// appendix on a phone, where a fixed page in a scrolling box means pinching at -/// six-point type. It reflows, and the browser's own find works on it. +/// Readability leads. Most of what this wiki carries a PDF for is read on a +/// phone at a meeting, and there the browser's viewer is a fixed page in a +/// scrolling box: pinching at six-point type, no reflow, and the page's own find +/// blind to it. The native one reflows, works with find, keeps the page marks, +/// and is the one this app can go on improving. +/// +/// The browser's stays one tap away and unchanged, for when a reader wants the +/// page exactly as it was laid out, or wants to print it. #[derive(Clone, Copy, PartialEq, Debug)] pub enum PdfViewer { - /// The browser's built-in viewer, in an iframe. The default. + /// The browser's built-in viewer, in an iframe. Browser, - /// Rendered here, from the file's own bytes, as flowing text. + /// Rendered here, from the file's own bytes, as flowing text. The default. Native, } @@ -288,12 +291,11 @@ impl PdfViewer { } } - /// Anything unrecognised is the browser: a preference that cannot be read - /// must not silently move a reader to the renderer that reflows. + /// Anything unrecognised is the default, which is this app's own renderer. fn from_key(key: &str) -> Self { match key { - "native" => PdfViewer::Native, - _ => PdfViewer::Browser, + "browser" => PdfViewer::Browser, + _ => PdfViewer::Native, } } } @@ -304,7 +306,7 @@ pub static PDF_VIEWER: GlobalSignal = Signal::global(|| { .and_then(|w| w.local_storage().ok().flatten()) .and_then(|s| s.get_item("wiki_pdf_viewer").ok().flatten()) .map(|v| PdfViewer::from_key(&v)) - .unwrap_or(PdfViewer::Browser) + .unwrap_or(PdfViewer::Native) }); /// Choose a PDF viewer, and remember it. @@ -1224,7 +1226,7 @@ pub fn FileApp(node: NodeWithChildren) -> Element { if file_mime == "application/pdf" { super::widgets::SheetGroup { div { class: "sheet-label", "{t(\"file.renderedBy\")}" } - for viewer in [PdfViewer::Browser, PdfViewer::Native] { + for viewer in [PdfViewer::Native, PdfViewer::Browser] { button { key: "{viewer.label_key()}", class: if PDF_VIEWER() == viewer { "sheet-action selected" } else { "sheet-action" }, diff --git a/src/components/folder.rs b/src/components/folder.rs index 1dd290ed..37a9a1fd 100644 --- a/src/components/folder.rs +++ b/src/components/folder.rs @@ -807,7 +807,7 @@ pub fn FolderApp( // The folder's contents in a separate card, so the content card's header // stays simple (identity + tools). The item count and the list/grid toggle // live here, with the children they control. - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "folder_open" } @@ -898,7 +898,7 @@ pub fn FolderApp( // What this group is credited on, below its own contents. if is_group { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "history_edu" } } // The neutral heading, not the first-person one: these are the diff --git a/src/components/loader.rs b/src/components/loader.rs index 5e2d8163..281a2852 100644 --- a/src/components/loader.rs +++ b/src/components/loader.rs @@ -234,7 +234,7 @@ fn PathResolver(segments: Vec, app: Option) -> Element { #[component] fn ContentSkeleton() -> Element { rsx! { - div { class: "card", + div { class: "card app-card", div { class: "skeleton-card", div { class: "skeleton-row", div { class: "skeleton skeleton-avatar" } diff --git a/src/components/member.rs b/src/components/member.rs index d5070acf..a5b90c8b 100644 --- a/src/components/member.rs +++ b/src/components/member.rs @@ -114,7 +114,7 @@ pub fn MemberApp(node: NodeWithChildren) -> Element { super::widgets::SupportingPaneLayout { primary: rsx! { if show_link_nudge && !nudge_dismissed() { - div { class: "card mb-1", + div { class: "card app-card mb-1", div { class: "card-content stack stack-h", span { class: "material-icons", "cloud_off" } @@ -144,7 +144,7 @@ pub fn MemberApp(node: NodeWithChildren) -> Element { } } } - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar", span { class: "material-icons", "group" } } div { diff --git a/src/components/profile.rs b/src/components/profile.rs index f746609a..b1b36b58 100644 --- a/src/components/profile.rs +++ b/src/components/profile.rs @@ -75,7 +75,7 @@ pub fn ProfileApp() -> Element { let Some(user) = user else { return rsx! { - div { class: "card", + div { class: "card app-card", div { class: "card-content", p { class: "body-large", "{t(\"node.maybeLoginForAccess\")}" } Link { to: Route::Login {}, class: "btn btn-primary", "{t(\"common.logIn\")}" } @@ -89,7 +89,7 @@ pub fn ProfileApp() -> Element { let show_linked = link.linked && !*just_unlinked.read(); rsx! { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar", {user_avatar(&user.avatar_url, icon_el("app/profile"))} } div { @@ -114,7 +114,7 @@ pub fn ProfileApp() -> Element { // action; otherwise hand off to the backend OAuth flow with the handle + // current NHost access token (it redirects back to APP_ORIGIN with // ?linked=bluesky|error, surfaced in a snackbar by App on load). - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", {crate::components::loader::bsky_logo()} } h3 { class: "title-medium", @@ -228,7 +228,7 @@ pub fn ProfileApp() -> Element { // Background Web Push: opt this device in/out of notifications that arrive // even when the app is closed (e.g. a vote opening in a group you're in). if crate::pwa::push_supported() { - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "notifications" } } h3 { class: "title-medium", "{t(\"profile.notifications\")}" } @@ -279,7 +279,7 @@ pub fn ProfileApp() -> Element { } // Latest contributions the user authored, each linking to the item. - div { class: "card", + div { class: "card app-card", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "history_edu" } } h3 { class: "title-medium", "{t(\"profile.contributions\")}" } @@ -478,7 +478,7 @@ pub fn UserProfile(id: String) -> Element { let contrib_state = contributions.read().clone(); rsx! { - div { class: "card", + div { class: "card app-card", div { class: "profile-hero", div { class: "profile-hero-avatar", {user_avatar(&avatar_url, icon_el("wiki/user"))} @@ -503,7 +503,7 @@ pub fn UserProfile(id: String) -> Element { // What they have written, first: it is why you opened someone's profile. // The same card your own profile carries, but titled without the "Your", // since this is someone else's page. - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", div { class: "avatar small", span { class: "material-icons", "history_edu" } } h3 { class: "title-medium", "{t(\"profile.contributionsOther\")}" } @@ -518,7 +518,7 @@ pub fn UserProfile(id: String) -> Element { }} } // Where you overlap, below it: context for the reader, not the point. - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", // join_inner, the set-intersection mark: this card is precisely // the overlap between their memberships and yours, and it does not diff --git a/src/components/screen.rs b/src/components/screen.rs index 9d2c2b7e..7eb0d7c2 100644 --- a/src/components/screen.rs +++ b/src/components/screen.rs @@ -194,7 +194,7 @@ pub fn ScreenApp(node: NodeWithChildren) -> Element { // the chair asked for this one, and the active node is often // just whatever was last discussed. _ if show_feed => rsx! { - div { class: "card projector-feed", + div { class: "card app-card projector-feed", div { class: "card-header", div { class: "avatar", span { class: "material-icons", "view_agenda" } } h3 { class: "title-medium", "{crate::i18n::t(\"layout.feed\")}" } @@ -208,7 +208,7 @@ pub fn ScreenApp(node: NodeWithChildren) -> Element { // The LARGE orb on purpose: this is the projector, read // from across a room, and is the one place a full-size // empty state belongs outside a page-level state. - div { class: "card", + div { class: "card app-card", div { class: "empty-state", div { class: "empty-state-orb", span { class: "material-icons", "cast" } diff --git a/src/components/social.rs b/src/components/social.rs index 83abce98..4c431fa2 100644 --- a/src/components/social.rs +++ b/src/components/social.rs @@ -61,7 +61,7 @@ pub fn SocialApp(node: NodeWithChildren) -> Element { } }, Some(Ok(_)) => rsx! { - div { class: "card", + div { class: "card app-card", super::widgets::EmptyState { icon: "search_off".to_string(), message: t("social.empty"), @@ -73,7 +73,7 @@ pub fn SocialApp(node: NodeWithChildren) -> Element { // Log the detail; the UI shows a friendly state, never raw error text. crate::errors::log_handled("bluesky wall fetch", e); rsx! { - div { class: "card accent-error", + div { class: "card app-card accent-error", super::widgets::ErrorState { title: t("error.somethingWentWrong"), small: true, diff --git a/src/components/speak.rs b/src/components/speak.rs index d3639d08..c3be0cda 100644 --- a/src/components/speak.rs +++ b/src/components/speak.rs @@ -632,7 +632,7 @@ fn SpeakList( // Owner admin panel (#6): open/close the list, clear it, run the timer. if is_owner && !screen { - div { class: "card", + div { class: "card app-card", div { class: "card-header", h3 { class: "title-medium", "{t(\"speak.manageSpeakerList\")}" } } diff --git a/src/components/vote/policy.rs b/src/components/vote/policy.rs index 083c60cc..c61df689 100644 --- a/src/components/vote/policy.rs +++ b/src/components/vote/policy.rs @@ -66,7 +66,7 @@ pub fn PolicyApp(node: NodeWithChildren, path: Vec) -> Element { // Amendments — always shown so its create action (in the header) has a // home; the body shows an empty state until the first amendment lands. - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", div { class: "avatar small", {icon_el("vote/change")} } h3 { class: "title-medium", "{t(\"vote.amendments\")}" } @@ -174,7 +174,7 @@ pub fn PolicyApp(node: NodeWithChildren, path: Vec) -> Element { // Polls if !polls.is_empty() { - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", div { class: "avatar small", {icon_el("vote/poll")} } h3 { class: "title-medium", "{t(\"mime.vote\")}" } @@ -213,7 +213,7 @@ pub fn PolicyApp(node: NodeWithChildren, path: Vec) -> Element { // Other children (comments, questions) if !comments.is_empty() { - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "list", for child in comments.iter() { { diff --git a/src/components/vote/poll.rs b/src/components/vote/poll.rs index 34ba4ff9..af71d96f 100644 --- a/src/components/vote/poll.rs +++ b/src/components/vote/poll.rs @@ -864,7 +864,7 @@ pub(super) fn StartPollButton(node: NodeWithChildren, path: Vec) -> Elem let range_label = t("poll.voteRange"); rsx! { - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", // `small`, like every other section card on the pages this sits // among (candidates, questions, amendments, polls): a full-size diff --git a/src/components/vote/position.rs b/src/components/vote/position.rs index af040ee2..68dd2403 100644 --- a/src/components/vote/position.rs +++ b/src/components/vote/position.rs @@ -190,7 +190,7 @@ pub fn PositionApp(node: NodeWithChildren, path: Vec) -> Element { // Polls opened on this position. if !polls.is_empty() { - div { class: "card mt-1", + div { class: "card app-card mt-1", div { class: "card-header", div { class: "avatar small", {icon_el("vote/poll")} } h3 { class: "title-medium", "{t(\"mime.vote\")}" }