atproto blogging
1use miette::IntoDiagnostic;
2pub use weaver_api::sh_weaver::notebook::colour_scheme::{ColourScheme, ColourSchemeColours};
3use weaver_api::sh_weaver::notebook::theme::Font;
4use weaver_api::sh_weaver::notebook::theme::FontName;
5use weaver_api::sh_weaver::notebook::theme::FontValue;
6pub use weaver_api::sh_weaver::notebook::theme::{
7 Theme, ThemeDarkCodeTheme, ThemeFonts, ThemeLightCodeTheme, ThemeSpacing,
8};
9use weaver_common::jacquard::CowStr;
10use weaver_common::jacquard::IntoStatic;
11use weaver_common::jacquard::client::AgentSession;
12use weaver_common::jacquard::cowstr::ToCowStr;
13use weaver_common::jacquard::prelude::*;
14
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum ThemeDefault {
17 #[default]
18 Auto,
19 Light,
20 Dark,
21}
22
23/// A theme with resolved colour schemes (no strongRefs, actual data)
24#[derive(Clone, Debug)]
25pub struct ResolvedTheme<'a> {
26 pub default: ThemeDefault,
27 pub dark_scheme: ColourSchemeColours<'a>,
28 pub light_scheme: ColourSchemeColours<'a>,
29 pub fonts: ThemeFonts<'a>,
30 pub spacing: ThemeSpacing<'a>,
31 pub dark_code_theme: ThemeDarkCodeTheme<'a>,
32 pub light_code_theme: ThemeLightCodeTheme<'a>,
33}
34
35pub fn default_colour_scheme_dark() -> ColourSchemeColours<'static> {
36 ColourSchemeColours {
37 base: CowStr::new_static("#191724"),
38 surface: CowStr::new_static("#1f1d2e"),
39 overlay: CowStr::new_static("#26233a"),
40 text: CowStr::new_static("#e0def4"),
41 muted: CowStr::new_static("#6e6a86"),
42 subtle: CowStr::new_static("#908caa"),
43 emphasis: CowStr::new_static("#e0def4"),
44 primary: CowStr::new_static("#c4a7e7"),
45 secondary: CowStr::new_static("#9ccfd8"),
46 tertiary: CowStr::new_static("#ebbcba"),
47 error: CowStr::new_static("#eb6f92"),
48 warning: CowStr::new_static("#f6c177"),
49 success: CowStr::new_static("#31748f"),
50 border: CowStr::new_static("#403d52"),
51 link: CowStr::new_static("#ebbcba"),
52 highlight: CowStr::new_static("#524f67"),
53 ..Default::default()
54 }
55}
56
57pub fn default_fonts() -> ThemeFonts<'static> {
58 ThemeFonts {
59 // Serif for body text, sans for headings/UI
60 body: vec![
61 Font::new()
62 .value(FontValue::FontName(
63 CowStr::new_static("Adobe Caslon Pro").into(),
64 ))
65 .build(),
66 Font::new()
67 .value(FontValue::FontName(
68 CowStr::new_static("Latin Modern Roman").into(),
69 ))
70 .build(),
71 Font::new()
72 .value(FontValue::FontName(
73 CowStr::new_static("Times New Roman").into(),
74 ))
75 .build(),
76 Font::new()
77 .value(FontValue::FontName(CowStr::new_static("serif").into()))
78 .build(),
79 ],
80 heading: vec![
81 Font::new()
82 .value(FontValue::FontName(
83 CowStr::new_static("IBM Plex Sans").into(),
84 ))
85 .build(),
86 Font::new()
87 .value(FontValue::FontName(CowStr::new_static("system-ui").into()))
88 .build(),
89 Font::new()
90 .value(FontValue::FontName(CowStr::new_static("sans-serif").into()))
91 .build(),
92 ],
93 monospace: vec![
94 Font::new()
95 .value(FontValue::FontName(
96 CowStr::new_static("Ioskeley Mono").into(),
97 ))
98 .build(),
99 Font::new()
100 .value(FontValue::FontName(
101 CowStr::new_static("IBM Plex Mono").into(),
102 ))
103 .build(),
104 Font::new()
105 .value(FontValue::FontName(
106 CowStr::new_static("Berkeley Mono").into(),
107 ))
108 .build(),
109 Font::new()
110 .value(FontValue::FontName(CowStr::new_static("Consolas").into()))
111 .build(),
112 Font::new()
113 .value(FontValue::FontName(CowStr::new_static("monospace").into()))
114 .build(),
115 ],
116 extra_data: None,
117 }
118}
119
120pub fn default_spacing() -> ThemeSpacing<'static> {
121 ThemeSpacing {
122 base_size: CowStr::new_static("16px"),
123 line_height: CowStr::new_static("1.6"),
124 scale: CowStr::new_static("1.25"),
125 ..Default::default()
126 }
127}
128
129pub fn default_colour_scheme_light() -> ColourSchemeColours<'static> {
130 // Rose Pine Dawn with moderate contrast text (text/muted/subtle/emphasis darkened)
131 ColourSchemeColours {
132 base: CowStr::new_static("#faf4ed"),
133 surface: CowStr::new_static("#fffaf3"),
134 overlay: CowStr::new_static("#f2e9e1"),
135 // Text colors darkened for better contrast
136 text: CowStr::new_static("#1f1d2e"),
137 muted: CowStr::new_static("#635e74"),
138 subtle: CowStr::new_static("#4a4560"),
139 emphasis: CowStr::new_static("#1e1a2d"),
140 // Accent colors kept at original Rose Pine Dawn values
141 primary: CowStr::new_static("#907aa9"),
142 secondary: CowStr::new_static("#56949f"),
143 tertiary: CowStr::new_static("#286983"),
144 error: CowStr::new_static("#b4637a"),
145 warning: CowStr::new_static("#ea9d34"),
146 success: CowStr::new_static("#286983"),
147 border: CowStr::new_static("#dfdad9"),
148 link: CowStr::new_static("#d7827e"),
149 highlight: CowStr::new_static("#cecacd"),
150 ..Default::default()
151 }
152}
153
154pub fn default_resolved_theme() -> ResolvedTheme<'static> {
155 ResolvedTheme {
156 default: ThemeDefault::Auto,
157 dark_scheme: default_colour_scheme_dark(),
158 light_scheme: default_colour_scheme_light(),
159 fonts: default_fonts(),
160 spacing: default_spacing(),
161 dark_code_theme: ThemeDarkCodeTheme::CodeThemeName(Box::new("rose-pine".to_cowstr())),
162 light_code_theme: ThemeLightCodeTheme::CodeThemeName(Box::new(
163 "rose-pine-dawn".to_cowstr(),
164 )),
165 }
166}
167
168/// Resolve a theme by fetching its colour scheme records from the PDS
169pub async fn resolve_theme<A: AgentSession + IdentityResolver>(
170 agent: &A,
171 theme: &Theme<'_>,
172) -> miette::Result<ResolvedTheme<'static>> {
173 use weaver_common::jacquard::client::AgentSessionExt;
174
175 // Fetch dark scheme
176 let dark_response = agent
177 .get_record::<ColourScheme>(&theme.dark_scheme.uri)
178 .await
179 .into_diagnostic()?;
180
181 let dark_scheme: ColourScheme = dark_response.into_output().into_diagnostic()?.into();
182
183 // Fetch light scheme
184 let light_response = agent
185 .get_record::<ColourScheme>(&theme.light_scheme.uri)
186 .await
187 .into_diagnostic()?;
188
189 let light_scheme: ColourScheme = light_response.into_output().into_diagnostic()?.into();
190 let default = match theme.default_theme.as_ref().map(|t| t.as_str()) {
191 Some("auto") => ThemeDefault::Auto,
192 Some("dark") => ThemeDefault::Dark,
193 Some("light") => ThemeDefault::Light,
194 _ => ThemeDefault::Auto,
195 };
196
197 Ok(ResolvedTheme {
198 default,
199 dark_scheme: dark_scheme.colours.into_static(),
200 light_scheme: light_scheme.colours.into_static(),
201 fonts: theme.fonts.clone().into_static(),
202 spacing: theme.spacing.clone().into_static(),
203 dark_code_theme: theme.dark_code_theme.clone().into_static(),
204 light_code_theme: theme.light_code_theme.clone().into_static(),
205 })
206}