Update branding

Update the Ferese symbols, appearance-specific wallpapers, and rounded wallpaper and theme previews. Bundle Figtree and add the Ferese name to the website header.

Verified configuration, theme, Settings, wallpaper and installer tests, both UI renderers, the static site build, and browser layouts.
This commit is contained in:
Samuel Onoja committed 2026-10-04 11:11:14 +01:00
1 parent f81d8d50ea
commit 3533fac521
35 files changed
+1212 -309

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1058" height="650" viewBox="105 314 1058 650" role="img" aria-labelledby="title desc">
<title id="title">Ferese orbit</title>
<desc id="desc">Open, tilted orbit symbol. Smooth cubic Bezier vector paths; transparent background.</desc>
<title id="title">Ferese</title>
<desc id="desc">Ferese symbol with smooth cubic Bezier paths and a transparent background.</desc>
<path id="sweep-left" fill="currentColor" d="M 698 380 C 585 378 397 451 280 530 C 197 586 145 655 145 724 C 145 811 227 867 355 857 C 299 803 295 755 325 689 C 367 598 462 520 567 467 C 620 440 670 423 712 407 C 732 399 736 391 721 385 C 715 382 707 380 698 380 Z"/>
<path id="sweep-right" fill="currentColor" d="M 795 373 C 849 354 922 351 982 358 C 1071 368 1123 404 1123 461 C 1123 552 1027 681 906 780 C 782 881 643 927 524 921 C 453 918 397 898 355 857 C 496 845 671 797 807 720 C 932 649 992 573 986 516 C 980 456 928 423 844 408 C 821 404 799 401 787 395 C 774 389 780 379 795 373 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 890 B

After

Width:  |  Height:  |  Size: 874 B

+75 -19
View File
@@ -32,11 +32,17 @@ pub fn tile<M: Clone + 'static>(
font,
id,
} = options;
use cosmic::widget::{button, column, row};
let mut caption = row([])
.spacing(6)
.align_y(cosmic::iced::Alignment::Center)
.push(crate::text(family.name.clone(), font).size(12));
use cosmic::widget::{button, container, row};
let label_color = family
.palette(variant.unwrap_or(Appearance::Light))
.or(family.dark.as_ref())
.map(|palette| crate::parse_color(&palette.text).unwrap())
.unwrap_or(palette.text);
let mut caption = row([]).spacing(6).align_y(cosmic::iced::Alignment::Center).push(
crate::text(family.name.clone(), font)
.size(12)
.class(cosmic::theme::Text::Color(label_color)),
);
if family.light.is_none() || family.dark.is_none() {
caption = caption.push(
crate::text(
@@ -48,19 +54,31 @@ pub fn tile<M: Clone + 'static>(
font,
)
.size(10)
.class(cosmic::theme::Text::Color(palette.muted)),
.class(cosmic::theme::Text::Color(label_color)),
);
}
let image = preview(family, variant, active, selected);
let image =
cosmic::widget::responsive(move |size| image.clone().height(Length::Fixed(size.width * 126. / 200.)).into())
.height(Length::Shrink);
let content = column([]).spacing(2).push(image).push(caption);
let family_preview = family.clone();
let caption: Element<'static, M> = container(caption)
.padding(8)
.width(Length::Fill)
.height(Length::Fill)
.align_y(cosmic::iced::alignment::Vertical::Bottom)
.into();
let image = cosmic::widget::responsive(move |size| {
let height = size.width * 126. / 200.;
// Match the button's logical radius when scaling the cached SVG artwork.
let radius = 10. * 200. / size.width.max(1.);
preview_with_radius(&family_preview, variant, active, selected, radius)
.height(Length::Fixed(height))
.into()
})
.height(Length::Shrink);
let content = cosmic::iced::widget::stack([image.into(), caption]);
let tile = button::custom(content)
.id(id.clone())
.name(format!("{}, {}", family.name, family.availability()))
.width(Length::Fill)
.padding(8)
.padding(0)
.class(tile_style(palette, selected))
.on_press(on_select);
radio_with_tab_stop(tile, id, selected, tab_stop, navigate)
@@ -70,11 +88,17 @@ fn tile_style(palette: crate::Palette, selected: bool) -> cosmic::theme::Button
let style = move |focused: bool, hovered: bool| cosmic::widget::button::Style {
shape: Some(BorderShape::Continuous),
outline: None,
background: Some(if hovered { palette.sidebar } else { palette.card }.into()),
border_width: if selected { 2. } else { 0. },
background: None,
border_width: if selected || focused {
2.
} else if hovered {
1.
} else {
0.
},
border_color: palette.accent,
border_radius: 10.into(),
outline_width: if focused { 2. } else { 0. },
outline_width: 0.,
outline_color: palette.text,
text_color: Some(palette.text),
icon_color: Some(palette.text),
@@ -93,15 +117,26 @@ pub fn preview(
variant: Option<Appearance>,
active: Option<Appearance>,
selected: bool,
) -> cosmic::widget::icon::Icon {
preview_with_radius(family, variant, active, selected, 10.)
}
fn preview_with_radius(
family: &Family,
variant: Option<Appearance>,
active: Option<Appearance>,
selected: bool,
radius: f32,
) -> cosmic::widget::icon::Icon {
use cosmic::widget::icon;
let key = format!("{}/{variant:?}/{active:?}/{selected}", family.id);
let key = format!("{}/{variant:?}/{active:?}/{selected}/{}", family.id, radius.to_bits());
static CACHE: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, (Family, icon::Handle)>>> =
std::sync::OnceLock::new();
let cache = CACHE.get_or_init(Default::default);
let mut cache = cache.lock().unwrap();
if cache.get(&key).is_none_or(|(cached, _)| cached != family) {
let handle = icon::from_svg_bytes(svg(family, variant, active, selected).into_bytes()).symbolic(false);
let handle = icon::from_svg_bytes(svg_with_radius(family, variant, active, selected, radius).into_bytes())
.symbolic(false);
cache.insert(key.clone(), (family.clone(), handle));
}
let handle = cache[&key].1.clone();
@@ -130,11 +165,32 @@ fn desktop(p: &Palette) -> String {
}
pub fn svg(family: &Family, variant: Option<Appearance>, active: Option<Appearance>, selected: bool) -> String {
svg_with_radius(family, variant, active, selected, 10.)
}
fn svg_with_radius(
family: &Family,
variant: Option<Appearance>,
active: Option<Appearance>,
selected: bool,
radius: f32,
) -> String {
let mut result = String::from(
r##"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="126" viewBox="0 0 200 126"><defs>
<clipPath id="light"><rect width="100" height="126"/></clipPath><clipPath id="dark"><rect x="100" width="100" height="126"/></clipPath>
<pattern id="missing" width="8" height="8" patternUnits="userSpaceOnUse"><rect width="8" height="8" fill="#D9DDE2"/><path d="M-2 2l4-4M0 8l8-8M6 10l4-4" stroke="#C4C9D1"/></pattern></defs>"##,
<pattern id="missing" width="8" height="8" patternUnits="userSpaceOnUse"><rect width="8" height="8" fill="#D9DDE2"/><path d="M-2 2l4-4M0 8l8-8M6 10l4-4" stroke="#C4C9D1"/></pattern>"##,
);
let outline =
cosmic::iced::border::Outline::new([0., 0., 200., 126.], [f64::from(radius); 4], BorderShape::Continuous)
.expect("finite preview dimensions");
// Rasterized once per cached preview; no shaped rendering layer per tile.
let points = outline.polygon(0.02).expect("positive contour tolerance");
result.push_str("<clipPath id=\"preview\"><polygon points=\"");
for [x, y] in points {
use std::fmt::Write as _;
write!(result, "{x:.4},{y:.4} ").unwrap();
}
result.push_str("\"/></clipPath></defs><g clip-path=\"url(#preview)\">");
for appearance in [Appearance::Light, Appearance::Dark] {
if variant.is_some_and(|variant| variant != appearance) {
continue;
@@ -196,7 +252,7 @@ pub fn svg(family: &Family, variant: Option<Appearance>, active: Option<Appearan
));
result.push_str(&format!(r#"<circle cx="183" cy="16" r="10" fill="{}"/><path d="M178 16l3 3 6-6" fill="none" stroke="{foreground}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>"#, p.accent));
}
result.push_str("</svg>");
result.push_str("</g></svg>");
result
}