From da7ca48ab48f24e5f93ac2245ec4e642a4895b46 Mon Sep 17 00:00:00 2001 From: CRT Date: Sat, 24 Oct 2026 19:53:06 +0800 Subject: [PATCH] desktop tray: card as tall as its content, card and menu open from the tray icon's edge (never over the taskbar); menu permission files --- .../permissions/autogenerated/menu_close.toml | 11 +++++ .../autogenerated/menu_close_subs.toml | 11 +++++ .../autogenerated/menu_dismiss.toml | 11 +++++ .../autogenerated/menu_open_sub.toml | 11 +++++ .../permissions/autogenerated/menu_pin.toml | 11 +++++ .../autogenerated/menu_pin_sub.toml | 11 +++++ .../permissions/autogenerated/menu_ready.toml | 11 +++++ .../autogenerated/menu_select.toml | 11 +++++ .../permissions/autogenerated/menu_state.toml | 11 +++++ apps/desktop/src-tauri/src/card.rs | 47 +++++++++++++++---- apps/desktop/src-tauri/src/commands.rs | 4 ++ apps/desktop/src-tauri/src/lib.rs | 15 +++--- apps/desktop/src-tauri/src/menu_panel.rs | 11 +++-- apps/desktop/src-tauri/tests/menu_panel.rs | 11 +++++ apps/desktop/src/card/Card.tsx | 14 +++++- apps/desktop/src/card/card-api.ts | 2 +- apps/desktop/src/card/card.css | 8 ++-- 17 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_close.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_close_subs.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_dismiss.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_open_sub.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_pin.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_pin_sub.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_ready.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_select.toml create mode 100644 apps/desktop/src-tauri/permissions/autogenerated/menu_state.toml diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_close.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_close.toml new file mode 100644 index 0000000..f113d1c --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_close.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-close" +description = "Enables the menu_close command without any pre-configured scope." +commands.allow = ["menu_close"] + +[[permission]] +identifier = "deny-menu-close" +description = "Denies the menu_close command without any pre-configured scope." +commands.deny = ["menu_close"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_close_subs.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_close_subs.toml new file mode 100644 index 0000000..dab5968 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_close_subs.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-close-subs" +description = "Enables the menu_close_subs command without any pre-configured scope." +commands.allow = ["menu_close_subs"] + +[[permission]] +identifier = "deny-menu-close-subs" +description = "Denies the menu_close_subs command without any pre-configured scope." +commands.deny = ["menu_close_subs"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_dismiss.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_dismiss.toml new file mode 100644 index 0000000..5c7a0ef --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_dismiss.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-dismiss" +description = "Enables the menu_dismiss command without any pre-configured scope." +commands.allow = ["menu_dismiss"] + +[[permission]] +identifier = "deny-menu-dismiss" +description = "Denies the menu_dismiss command without any pre-configured scope." +commands.deny = ["menu_dismiss"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_open_sub.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_open_sub.toml new file mode 100644 index 0000000..ee4175b --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_open_sub.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-open-sub" +description = "Enables the menu_open_sub command without any pre-configured scope." +commands.allow = ["menu_open_sub"] + +[[permission]] +identifier = "deny-menu-open-sub" +description = "Denies the menu_open_sub command without any pre-configured scope." +commands.deny = ["menu_open_sub"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_pin.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_pin.toml new file mode 100644 index 0000000..6e1dabc --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_pin.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-pin" +description = "Enables the menu_pin command without any pre-configured scope." +commands.allow = ["menu_pin"] + +[[permission]] +identifier = "deny-menu-pin" +description = "Denies the menu_pin command without any pre-configured scope." +commands.deny = ["menu_pin"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_pin_sub.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_pin_sub.toml new file mode 100644 index 0000000..b339f12 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_pin_sub.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-pin-sub" +description = "Enables the menu_pin_sub command without any pre-configured scope." +commands.allow = ["menu_pin_sub"] + +[[permission]] +identifier = "deny-menu-pin-sub" +description = "Denies the menu_pin_sub command without any pre-configured scope." +commands.deny = ["menu_pin_sub"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_ready.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_ready.toml new file mode 100644 index 0000000..c839ac0 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_ready.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-ready" +description = "Enables the menu_ready command without any pre-configured scope." +commands.allow = ["menu_ready"] + +[[permission]] +identifier = "deny-menu-ready" +description = "Denies the menu_ready command without any pre-configured scope." +commands.deny = ["menu_ready"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_select.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_select.toml new file mode 100644 index 0000000..e546f01 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_select.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-select" +description = "Enables the menu_select command without any pre-configured scope." +commands.allow = ["menu_select"] + +[[permission]] +identifier = "deny-menu-select" +description = "Denies the menu_select command without any pre-configured scope." +commands.deny = ["menu_select"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/menu_state.toml b/apps/desktop/src-tauri/permissions/autogenerated/menu_state.toml new file mode 100644 index 0000000..b4844a3 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/menu_state.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-menu-state" +description = "Enables the menu_state command without any pre-configured scope." +commands.allow = ["menu_state"] + +[[permission]] +identifier = "deny-menu-state" +description = "Denies the menu_state command without any pre-configured scope." +commands.deny = ["menu_state"] diff --git a/apps/desktop/src-tauri/src/card.rs b/apps/desktop/src-tauri/src/card.rs index 0e60f4c..e7c85ff 100644 --- a/apps/desktop/src-tauri/src/card.rs +++ b/apps/desktop/src-tauri/src/card.rs @@ -62,7 +62,7 @@ pub fn place(tray: Rect, work: Rect, card_w: f64, card_h: f64, gap: f64) -> (f64 /// Whether a card action word is one the card may send. pub fn valid_action(a: &str) -> bool { - matches!(a, "hide" | "open" | "route" | "refresh" | "mute" | "quit" | "external" | "ready") + matches!(a, "hide" | "open" | "route" | "refresh" | "mute" | "quit" | "external" | "ready" | "size") } // ---- the last snapshot ---- @@ -123,6 +123,38 @@ mod window { Ok(()) } + /// The icon rectangle of the last show, so a size change keeps the card anchored to the icon. + static LAST_ICON: Mutex> = Mutex::new(None); + + /// Card height in CSS px (what the page measured), clamped; the window keeps WIDTH. + fn height_px(h: f64) -> f64 { h.clamp(120.0, HEIGHT) } + + /// Puts the card next to `tray` at its current height: its edge GAP from the icon, inside the work area. + fn anchor(app: &AppHandle, w: &tauri::WebviewWindow, tray: Rect, css_h: f64) { + let monitor = app.monitor_from_point(tray.x + tray.w / 2.0, tray.y + tray.h / 2.0).ok().flatten().or_else(|| w.primary_monitor().ok().flatten()); + if let Some(m) = monitor { + let s = m.scale_factor(); + let wa = m.work_area(); + let work = Rect { x: wa.position.x as f64, y: wa.position.y as f64, w: wa.size.width as f64, h: wa.size.height as f64 }; + let _ = w.set_size(tauri::PhysicalSize::new((WIDTH * s).round() as u32, (css_h * s).round() as u32)); + let (x, y) = place(tray, work, WIDTH * s, css_h * s, GAP * s); + let _ = w.set_position(PhysicalPosition::new(x as i32, y as i32)); + } + } + + /// The page measured its height: resize the window to it and keep it anchored (the bottom edge stays above a bottom + /// taskbar, no empty band under the content). + pub fn resize(app: &AppHandle, css_h: f64) { + let Some(w) = app.get_webview_window(LABEL) else { return }; + let h = height_px(css_h); + if let Ok(mut c) = CURRENT_H.lock() { *c = h; } + if let Some(tray) = LAST_ICON.lock().ok().and_then(|t| *t) { + anchor(app, &w, tray, h); + } + } + + static CURRENT_H: Mutex = Mutex::new(HEIGHT); + /// A left click on the tray icon: shows the card next to the icon, or hides it when it is open. pub fn toggle(app: &AppHandle, tray: Rect) { let Some(w) = app.get_webview_window(LABEL) else { return }; @@ -134,14 +166,9 @@ mod window { if just_hidden() { return; } - let monitor = app.monitor_from_point(tray.x + tray.w / 2.0, tray.y + tray.h / 2.0).ok().flatten().or_else(|| w.primary_monitor().ok().flatten()); - if let Some(m) = monitor { - let s = m.scale_factor(); - let wa = m.work_area(); - let work = Rect { x: wa.position.x as f64, y: wa.position.y as f64, w: wa.size.width as f64, h: wa.size.height as f64 }; - let (x, y) = place(tray, work, WIDTH * s, HEIGHT * s, GAP * s); - let _ = w.set_position(PhysicalPosition::new(x as i32, y as i32)); - } + if let Ok(mut t) = LAST_ICON.lock() { *t = Some(tray); } + let h = CURRENT_H.lock().map(|c| *c).unwrap_or(HEIGHT); + anchor(app, &w, tray, h); let _ = w.show(); let _ = w.set_focus(); let _ = app.emit_to(LABEL, "card-shown", ()); @@ -158,4 +185,4 @@ mod window { } #[cfg(desktop)] -pub use window::{create, hide, toggle}; +pub use window::{create, hide, resize, toggle}; diff --git a/apps/desktop/src-tauri/src/commands.rs b/apps/desktop/src-tauri/src/commands.rs index 9739e6a..5bfa27e 100644 --- a/apps/desktop/src-tauri/src/commands.rs +++ b/apps/desktop/src-tauri/src/commands.rs @@ -1355,6 +1355,10 @@ pub fn card_action(window: WebviewWindow, app: AppHandle, action: String, arg: O match action.as_str() { "hide" => crate::card::hide(&app), "ready" => { let _ = app.emit_to(policy::MAIN, "card-request", ()); } + "size" => { + let h: f64 = arg.as_deref().unwrap_or_default().parse().map_err(|_| Fail::host("bad size"))?; + crate::card::resize(&app, h); + } "open" => { crate::card::hide(&app); main(); } "refresh" => { let _ = app.emit_to(policy::MAIN, "tray-action", "read"); } "mute" => { let _ = app.emit_to(policy::MAIN, "tray-action", "mute-toggle"); } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index e616bdd..f45f3bc 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -67,19 +67,20 @@ fn tray(app: &tauri::App) -> tauri::Result<()> { .on_tray_icon_event(|t, e| { let TrayIconEvent::Click { button, button_state: MouseButtonState::Up, rect, position, .. } = e else { return }; let app = t.app_handle(); + // both open from the icon's rectangle (not the click point): their edge sits next to the icon, above a + // bottom taskbar, so nothing starts inside the taskbar + let scale = app.get_webview_window(policy::MAIN).and_then(|w| w.scale_factor().ok()).unwrap_or(1.0); + let p = rect.position.to_physical::(scale); + let s = rect.size.to_physical::(scale); + let icon = if s.width > 0.0 && s.height > 0.0 { card::Rect { x: p.x, y: p.y, w: s.width, h: s.height } } else { card::Rect { x: position.x, y: position.y, w: 1.0, h: 1.0 } }; match button { - // right click: the menu panels at the click point - MouseButton::Right => menu_panel::open_root(app, position.x, position.y), - // left click: the tray card next to the icon + MouseButton::Right => menu_panel::open_root(app, menu_panel::Anchor::Tray { x: icon.x, y: icon.y, w: icon.w, h: icon.h }), MouseButton::Left => { if app.get_webview_window(card::LABEL).is_none() { show_main(app); return; } - let scale = app.get_webview_window(policy::MAIN).and_then(|w| w.scale_factor().ok()).unwrap_or(1.0); - let p = rect.position.to_physical::(scale); - let s = rect.size.to_physical::(scale); - card::toggle(app, card::Rect { x: p.x, y: p.y, w: s.width, h: s.height }); + card::toggle(app, icon); } _ => {} } diff --git a/apps/desktop/src-tauri/src/menu_panel.rs b/apps/desktop/src-tauri/src/menu_panel.rs index 3809791..935ac09 100644 --- a/apps/desktop/src-tauri/src/menu_panel.rs +++ b/apps/desktop/src-tauri/src/menu_panel.rs @@ -107,6 +107,8 @@ pub fn find<'a>(nodes: &'a [Node], path: &[String]) -> Option<(&'a [Node], Optio pub enum Anchor { /// The tray click point (physical). Point { x: f64, y: f64 }, + /// The tray icon's rectangle (physical): the root opens from the icon's edge that faces the screen, like the card. + Tray { x: f64, y: f64, w: f64, h: f64 }, /// Beside a parent panel: its left and right edge and the row's top (physical). Beside { left: f64, right: f64, top: f64 }, /// A remembered top-left (pinned panels). @@ -129,6 +131,8 @@ pub fn place_menu(anchor: Anchor, size: (f64, f64), work: Rect) -> (f64, f64) { let (wx, wy, ww, wh) = work; let (x, y) = match anchor { Anchor::Point { x, y } => (if x + w > wx + ww { x - w } else { x }, if y + h > wy + wh { y - h } else { y }), + // same rule as the card: the panel's edge sits GAP from the icon, never over the taskbar + Anchor::Tray { x, y, w: iw, h: ih } => return crate::card::place(crate::card::Rect { x, y, w: iw, h: ih }, crate::card::Rect { x: wx, y: wy, w: ww, h: wh }, w, h, GAP), Anchor::Beside { left, right, top } => (if right + w <= wx + ww - GAP { right - 2.0 } else { left - w + 2.0 }, top - 5.0), Anchor::At { x, y } => (x, y), }; @@ -520,12 +524,12 @@ mod runtime { }); } - /// A right click on the tray icon: a fresh root menu at the click point (physical px). - pub fn open_root(app: &AppHandle, x: f64, y: f64) { + /// A right click on the tray icon: a fresh root menu next to the icon (`anchor`: Anchor::Tray, physical px). + pub fn open_root(app: &AppHandle, anchor: Anchor) { if native_fallback(app) { return; // the native menu is on the tray and opens by itself } - let (label, closes) = book(app, |b| b.open_root(Anchor::Point { x, y })); + let (label, closes) = book(app, |b| b.open_root(anchor)); destroy(app, &closes); let handle = app.clone(); std::thread::spawn(move || { @@ -571,6 +575,7 @@ mod runtime { fn anchor_point(a: Anchor) -> (f64, f64) { match a { Anchor::Point { x, y } | Anchor::At { x, y } => (x, y), + Anchor::Tray { x, y, w, h } => (x + w / 2.0, y + h / 2.0), Anchor::Beside { right, top, .. } => (right, top), } } diff --git a/apps/desktop/src-tauri/tests/menu_panel.rs b/apps/desktop/src-tauri/tests/menu_panel.rs index 0f89878..d46cd18 100644 --- a/apps/desktop/src-tauri/tests/menu_panel.rs +++ b/apps/desktop/src-tauri/tests/menu_panel.rs @@ -83,6 +83,17 @@ fn root_opens_away_from_the_taskbar_and_stays_inside() { assert_eq!(y, GAP); } +#[test] +fn tray_root_sits_just_above_the_icon_never_over_the_taskbar() { + // icon in a bottom taskbar (the icon is below the work area): bottom edge exactly GAP above the icon's top + let icon = Anchor::Tray { x: 1800.0, y: 1044.0, w: 32.0, h: 32.0 }; + let (_, y) = place_menu(icon, (260.0, 300.0), WORK); + assert_eq!(y + 300.0, 1040.0 - GAP); + // even when the reported work area wrongly includes the taskbar, the panel stays above the icon + let (_, y) = place_menu(icon, (260.0, 300.0), (0.0, 0.0, 1920.0, 1080.0)); + assert!(y + 300.0 <= 1044.0 - GAP); +} + #[test] fn submenus_open_beside_and_flip_left_at_the_edge() { let (x, _) = place_menu(Anchor::Beside { left: 1600.0, right: 1880.0, top: 100.0 }, (280.0, 200.0), WORK); diff --git a/apps/desktop/src/card/Card.tsx b/apps/desktop/src/card/Card.tsx index 99b4101..4140e6a 100644 --- a/apps/desktop/src/card/Card.tsx +++ b/apps/desktop/src/card/Card.tsx @@ -18,7 +18,7 @@ export interface CardHost { get: () => Promise; onSnapshot: (fn: (s: CardSnapshot) => void) => () => void; onShown: (fn: () => void) => () => void; - action: (action: 'hide' | 'open' | 'route' | 'refresh' | 'mute' | 'quit' | 'external' | 'ready', arg?: string) => void; + action: (action: 'hide' | 'open' | 'route' | 'refresh' | 'mute' | 'quit' | 'external' | 'ready' | 'size', arg?: string) => void; } const route = (app: string, tab: 'releases' | 'reviews' | 'health') => JSON.stringify({ screen: 'app', appId: app, tab }); @@ -39,6 +39,18 @@ export function Card({ host, lang }: { host: CardHost; lang: CardLang }) { const tm = setInterval(() => setNow(Date.now()), 30_000); return () => { a(); b(); clearInterval(tm); }; }, [host]); + // the window is as tall as the card: report the height whenever it changes (the host re-anchors it above the icon) + useEffect(() => { + const el = root.current; + if (!el || typeof ResizeObserver === 'undefined') return; + let last = 0; + const ro = new ResizeObserver(() => { + const h = Math.ceil(el.getBoundingClientRect().height); + if (h > 0 && h !== last) { last = h; host.action('size', String(h)); } + }); + ro.observe(el); + return () => ro.disconnect(); + }, [host]); // every show: fade in, focus the first control (keyboard users start at Refresh) useEffect(() => { (root.current?.querySelector('button:not([disabled])') ?? root.current)?.focus(); }, [shown, !!s]); diff --git a/apps/desktop/src/card/card-api.ts b/apps/desktop/src/card/card-api.ts index 6a66afc..86b466c 100644 --- a/apps/desktop/src/card/card-api.ts +++ b/apps/desktop/src/card/card-api.ts @@ -4,7 +4,7 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import type { CardSnapshot } from './model'; -export type CardAction = 'hide' | 'open' | 'route' | 'refresh' | 'mute' | 'quit' | 'external' | 'ready'; +export type CardAction = 'hide' | 'open' | 'route' | 'refresh' | 'mute' | 'quit' | 'external' | 'ready' | 'size'; export const inTauri = (): boolean => typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window; diff --git a/apps/desktop/src/card/card.css b/apps/desktop/src/card/card.css index c9502c8..68ca2e6 100644 --- a/apps/desktop/src/card/card.css +++ b/apps/desktop/src/card/card.css @@ -1,12 +1,12 @@ -/* The tray card (card.html): v2 language at 360 x 480, rounded, its own surface; fades in over 120 ms on every show. */ +/* The tray card (card.html): v2 language, 360 wide and as tall as its content (at most 480: the body scrolls), rounded, its own surface; fades in over 120 ms on every show. */ html:has(body.tc-page), body.tc-page { margin: 0; height: 100%; background: transparent; overflow: hidden; } body.tc-page { font: var(--ds-t-body)/1.4 system-ui, -apple-system, "Segoe UI", sans-serif; color: var(--ink); } -body.tc-page #card { height: 100%; } +body.tc-page #card { height: auto; } body.tc-page.tc-in .tc { animation: tc-in 120ms ease-out; } :root[data-motion="reduce"] body.tc-page.tc-in .tc { animation: none; } @keyframes tc-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } } -.tc { box-sizing: border-box; height: 100%; max-height: 480px; display: flex; flex-direction: column; border-radius: var(--ds-r-lg); +.tc { box-sizing: border-box; height: auto; max-height: 480px; display: flex; flex-direction: column; border-radius: var(--ds-r-lg); border: 1px solid var(--ds-stroke); background: radial-gradient(120% 60% at 100% 0%, color-mix(in srgb, var(--accent-solid) 10%, transparent), transparent 60%), var(--ds-raised); overflow: hidden; outline: none; } .tc-head { position: relative; display: flex; align-items: center; gap: 8px; padding: 14px 12px 12px 16px; border-bottom: 1px solid var(--ds-stroke); } .tc-title { flex: 1; min-width: 0; display: flex; flex-direction: column; } @@ -25,7 +25,7 @@ body.tc-page.tc-in .tc { animation: tc-in 120ms ease-out; } .tc-menu button:hover, .tc-menu button:focus-visible { background: var(--hover); outline: none; } .tc-menu hr { border: 0; border-top: 1px solid var(--ds-stroke); margin: 4px 2px; } -.tc-body { flex: 1; overflow-y: auto; padding: 6px 10px 12px; scrollbar-width: thin; } +.tc-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 6px 10px 12px; scrollbar-width: thin; } .tc-empty { margin: 28px 16px; text-align: center; color: var(--muted); } .tc-sec { margin-top: 8px; } .tc-sec h2 { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin: 6px 6px 4px; font-size: var(--ds-t-micro); font-weight: var(--ds-w-strong); text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }