返回 CodeWhale
scene.rs
根目录 / crates / tui / src / integrations / dsh / scene.rs
1 //! Ambient ocean scene for the DSH bundle: whales and Codewhale glyph fish
2 //! painted on a canvas behind the DSH web UI.
3 //!
4 //! dsh-client-modules serves exactly one file per client plugin
5 //! (`/plugins/<id>/client.js`), so the scene ships as a plain script fragment
6 //! (`scene.js`, checked in next to this module) that `skin::bundle_client_js`
7 //! splices into the client half. This module owns the fragment, the palette
8 //! the scene reads, and the "veil" — the handful of DSH background tokens the
9 //! client re-issues as translucent rgba so the canvas shows through faintly
10 //! while text stays on an opaque-enough surface.
11
12 use std::collections::BTreeMap;
13
14 use ratatui::style::Color;
15
16 use super::identity::sha256_hex;
17 use super::skin::SkinTokens;
18 use codewhale_palette::UiTheme;
19
20 /// `localStorage` key that turns the scene off in the browser (`"off"`).
21 pub(crate) const OCEAN_STORAGE_KEY: &str = "codewhale.ocean";
22 /// Body class that turns the scene off in the browser.
23 pub(crate) const OCEAN_OFF_CLASS: &str = "codewhale-ocean-off";
24 /// `window` handle the scene exposes (`start`, `stop`, `setIntensity`, `setScheme`).
25 pub(crate) const OCEAN_WINDOW_HANDLE: &str = "__codewhaleOcean";
26
27 /// Alpha of `--dsw-alias-bg-base` while the scene is on. The frame and the
28 /// centre column both paint this token, so the effective veil over the main
29 /// area is `1 - (1 - a)^2` (~0.66 at 0.42); dialogs that paint it once sit
30 /// at 0.42 over an opaque canvas that is itself the same base colour. This
31 /// keeps text surfaces calm while letting the Codewhale scene read clearly.
32 const BASE_VEIL_ALPHA: &str = "0.42";
33 /// Alpha of `--dsw-specific-sidebar-fill` (stacked over the frame).
34 const SIDEBAR_VEIL_ALPHA: &str = "0.78";
35
36 /// The scene script fragment: defines `createOcean(palette)`; no module
37 /// syntax, no top-level side effects.
38 pub(crate) fn bundle_scene_js() -> &'static str {
39 include_str!("scene.js")
40 }
41
42 pub(crate) fn scene_sha256() -> String {
43 sha256_hex(bundle_scene_js().as_bytes())
44 }
45
46 fn rgb(color: Color) -> Option<(u8, u8, u8)> {
47 match color {
48 Color::Rgb(r, g, b) => Some((r, g, b)),
49 _ => None,
50 }
51 }
52
53 fn hex(color: Color) -> String {
54 codewhale_palette::hex_rgb_string(color).unwrap_or_else(|| "#808080".to_string())
55 }
56
57 fn rgba(color: Color, alpha: &str) -> String {
58 match rgb(color) {
59 Some((r, g, b)) => format!("rgba({r},{g},{b},{alpha})"),
60 None => "inherit".to_string(),
61 }
62 }
63
64 /// Palette the scene reads: `{ light: {base, accent, ink, dim}, dark: {...} }`,
65 /// rendered from the same TUI palettes as the skin tokens.
66 pub(crate) fn ocean_palette() -> BTreeMap<&'static str, BTreeMap<&'static str, String>> {
67 fn one(theme: &UiTheme) -> BTreeMap<&'static str, String> {
68 let mut m = BTreeMap::new();
69 m.insert("base", hex(theme.surface_bg));
70 m.insert("accent", hex(theme.accent_primary));
71 m.insert("ink", hex(theme.text_body));
72 m.insert("dim", hex(theme.text_dim));
73 m
74 }
75 let (light, dark) = super::skin::browser_themes();
76 let mut map = BTreeMap::new();
77 map.insert("light", one(&light));
78 map.insert("dark", one(&dark));
79 map
80 }
81
82 pub(crate) fn ocean_palette_json() -> String {
83 serde_json::to_string_pretty(&ocean_palette()).expect("ocean palette is json")
84 }
85
86 /// Background tokens re-issued as translucent rgba while the scene is on.
87 /// Same colours as the skin table (`bg-base` = `surface_bg`; the sidebar
88 /// takes `panel_bg`), so with the scene off nothing shifts.
89 pub(crate) fn ocean_veil_tokens() -> BTreeMap<String, SkinTokens> {
90 let (light, dark) = super::skin::browser_themes();
91 let mut map = BTreeMap::new();
92 map.insert(
93 "--dsw-alias-bg-base".to_string(),
94 SkinTokens {
95 light: rgba(light.surface_bg, BASE_VEIL_ALPHA),
96 dark: rgba(dark.surface_bg, BASE_VEIL_ALPHA),
97 },
98 );
99 map.insert(
100 "--dsw-specific-sidebar-fill".to_string(),
101 SkinTokens {
102 light: rgba(light.panel_bg, SIDEBAR_VEIL_ALPHA),
103 dark: rgba(dark.panel_bg, SIDEBAR_VEIL_ALPHA),
104 },
105 );
106 map
107 }
108
109 pub(crate) fn ocean_veil_json() -> String {
110 serde_json::to_string_pretty(&ocean_veil_tokens()).expect("ocean veil is json")
111 }
112
112 lines RUST