Skip to main content

cvkg_core/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//   CVKG Extended: Section 2 of the CVKG Design Specification
24
25//! The View trait is the fundamental building block of CVKG. Every UI element — from a plain text label
26//! to a complex navigation controller — is a View. The trait is intentionally minimal; complexity emerges
27//! through modifier composition.
28//!
29//! # Conformance rules:
30//! 1. `body()` must be pure and side-effect free
31//! 2. Primitive views use `Never` as `Body` and register a `PaintCommand` directly with the scene graph
32//! 3. `View` types must implement `Send` but not necessarily `Sync`, enabling safe multi-threaded layout passes
33
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39pub mod future_views;
40pub mod security;
41
42pub use future_views::{HologramView, ParticleEmitter, StreamingText};
43
44/// Error state for fault isolation at the component level.
45#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
46pub struct ComponentErrorState {
47    pub has_error: bool,
48    pub error_message: Option<String>,
49    pub error_location: Option<String>,
50}
51impl ComponentErrorState {
52    pub fn clear() -> Self {
53        Self::default()
54    }
55
56    pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
57        Self {
58            has_error: true,
59            error_message: Some(message.into()),
60            error_location: Some(location.into()),
61        }
62    }
63}
64
65/// Knowledge state for the agentic memory system.
66#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
67pub struct KnowledgeState {
68    pub thoughts: Vec<String>,
69    pub actions: Vec<String>,
70    pub context: HashMap<String, String>,
71    pub last_query_results: Vec<KnowledgeId>,
72    #[serde(alias = "items")]
73    pub fragments: std::collections::HashMap<KnowledgeId, KnowledgeFragment>,
74    /// The Temporal Graph nodes
75    pub nodes: Vec<TemporalNode>,
76    /// The Temporal Graph edges
77    pub edges: Vec<TemporalEdge>,
78    /// The current operational Realm (Midgard/Asgard)
79    pub realm: Realm,
80    /// Last known pointer position (X, Y)
81    pub last_pointer_pos: [f32; 2],
82    /// Resolved pointer velocity (pixels per frame)
83    pub pointer_velocity: [f32; 2],
84    /// The current 'Focus' node ID (Odin's Eye focus)
85    pub odin_focus: Option<String>,
86    /// Agent attention heatmap (node_id -> intensity)
87    pub agent_attention: HashMap<String, f32>,
88    // Component state storage for dynamic state
89    #[serde(skip)]
90    pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
91    /// Global undo/redo manager tracking document and input states.
92    #[serde(skip)]
93    pub undo_manager: UndoManager,
94    /// Active notification list.
95    #[serde(default)]
96    pub notifications: Vec<Notification>,
97    /// Flag indicating whether the notification center panel is visible.
98    #[serde(default)]
99    pub notification_center_visible: bool,
100    /// Modifier key state: shift key pressed.
101    #[serde(default)]
102    pub modifiers_shift: bool,
103    /// Modifier key state: control key pressed.
104    #[serde(default)]
105    pub modifiers_ctrl: bool,
106    /// Modifier key state: alt/option key pressed.
107    #[serde(default)]
108    pub modifiers_alt: bool,
109    /// Modifier key state: logo/command/windows key pressed.
110    #[serde(default)]
111    pub modifiers_logo: bool,
112    /// Whether the performance profiling overlay (Cmd+Shift+P) is currently visible.
113    #[serde(default)]
114    pub performance_overlay_visible: bool,
115}
116
117impl KnowledgeState {
118    /// Apply activation decay to all temporal nodes and evolving components.
119    /// Nodes with weight below a threshold drift out of the primary context.
120    /// Components lose vitality (Fafnir's Decay) if not actively 'fed'.
121    pub fn apply_decay(&mut self, decay_factor: f32) {
122        for node in &mut self.nodes {
123            node.weight *= decay_factor;
124        }
125
126        // Fafnir's Decay: Components naturally revert to base state over time
127        for state in self.component_states.values() {
128            if let Ok(mut lock) = state.write()
129                && let Some(v) = lock.downcast_mut::<f32>()
130            {
131                *v = (*v * decay_factor).max(1.0);
132            }
133        }
134    }
135
136    /// Increase the importance weight of nodes associated with a successful task.
137    pub fn reinforce(&mut self, node_ids: &[String], boost: f32) {
138        for node in &mut self.nodes {
139            if node_ids.contains(&node.id) {
140                node.weight += boost;
141            }
142        }
143    }
144
145    /// Update pointer kinematics based on a new position.
146    pub fn update_pointer(&mut self, new_pos: [f32; 2]) {
147        self.pointer_velocity = [
148            new_pos[0] - self.last_pointer_pos[0],
149            new_pos[1] - self.last_pointer_pos[1],
150        ];
151        self.last_pointer_pos = new_pos;
152    }
153}
154// Knowledge System Types
155/// Unique identifier for knowledge fragments
156pub type KnowledgeId = String;
157
158/// A knowledge fragment stored in the memory system
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct KnowledgeFragment {
161    /// Unique identifier for this fragment
162    pub id: String,
163    /// Short summary for prompt injection and quick search
164    pub summary: String,
165    /// Reference source (e.g. filename, URL, or conversation ID)
166    pub source: String,
167    /// Frame number or timestamp of creation
168    pub created_at: u64,
169    /// Number of times this fragment has been retrieved
170    pub accessed_count: u32,
171    /// Full content (optional, can be loaded on-demand)
172    pub content: Option<String>,
173}
174
175impl KnowledgeFragment {
176    pub fn new(id: String, summary: String, source: String) -> Self {
177        Self {
178            id,
179            summary,
180            source,
181            created_at: 0,
182            accessed_count: 0,
183            content: None,
184        }
185    }
186}
187
188/// Memory layers for the layered cognitive engine
189#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
190pub enum MemoryLayer {
191    /// Raw mission events (short-term)
192    Episodic,
193    /// Extracted facts and tactical intelligence (long-term)
194    Semantic,
195    /// Successful command sequences and tool chains
196    Procedural,
197}
198
199/// The operational Realm of the UI.
200/// Midgard: Classic, functional, 2D tactical UI for mortals.
201/// Asgard: High-fidelity, cognitive, shader-heavy UI for the Singularity.
202#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
203pub enum Realm {
204    Midgard,
205    #[default]
206    Asgard,
207}
208
209/// Priority for screen reader announcements via `Renderer::announce`.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum AnnouncementPriority {
212    /// Wait for current speech to finish before announcing.
213    Polite,
214    /// Interrupt current speech to announce immediately.
215    Assertive,
216}
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct TemporalNode {
219    /// Unique identifier for this node
220    pub id: String,
221    /// ID of the underlying knowledge fragment
222    pub fragment_id: KnowledgeId,
223    /// Timestamp of the event
224    pub timestamp: u64,
225    /// The memory layer this node belongs to
226    pub layer: MemoryLayer,
227    /// Importance weight for activation decay and retrieval
228    pub weight: f32,
229}
230
231/// An edge in the Temporal Graph representing a relationship between nodes
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct TemporalEdge {
234    /// Source node ID
235    pub source: String,
236    /// Target node ID
237    pub target: String,
238    /// Type of relationship (e.g. "causal", "semantic", "temporal")
239    pub relation: String,
240    /// Weight/strength of the connection
241    pub weight: f32,
242}
243
244/// A single action group representing an undo/redo step.
245pub struct UndoGroup {
246    /// Descriptive label of the action (e.g. "Type", "Delete").
247    pub label: String,
248    /// Time when the action was recorded, in seconds.
249    pub timestamp: f32,
250    /// Closure to revert the action.
251    pub undo: Arc<dyn Fn() + Send + Sync>,
252    /// Closure to re-apply the action.
253    pub redo: Arc<dyn Fn() + Send + Sync>,
254}
255
256impl Clone for UndoGroup {
257    /// Clone the undo/redo group. The closures are shared via Arc.
258    fn clone(&self) -> Self {
259        Self {
260            label: self.label.clone(),
261            timestamp: self.timestamp,
262            undo: Arc::clone(&self.undo),
263            redo: Arc::clone(&self.redo),
264        }
265    }
266}
267
268impl std::fmt::Debug for UndoGroup {
269    /// Debug format helper to avoid printing closures.
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        f.debug_struct("UndoGroup")
272            .field("label", &self.label)
273            .field("timestamp", &self.timestamp)
274            .finish()
275    }
276}
277
278/// Unified manager for undo and redo stacks.
279/// Supports grouping of actions, max undo depth clamping, and coalescing.
280pub struct UndoManager {
281    /// History stack of undo/redo groups.
282    stack: Vec<UndoGroup>,
283    /// Current position/index in the stack.
284    position: usize,
285    /// Maximum allowed undo steps before discarding oldest.
286    max_depth: usize,
287    /// Time window in seconds to coalesce consecutive actions of the same type.
288    coalesce_window: f32,
289}
290
291impl Default for UndoManager {
292    /// Create a default UndoManager with a depth of 100 and a 0.5s coalesce window.
293    fn default() -> Self {
294        Self {
295            stack: Vec::new(),
296            position: 0,
297            max_depth: 100,
298            coalesce_window: 0.5,
299        }
300    }
301}
302
303impl Clone for UndoManager {
304    /// Clone the undo manager, preserving stacks and position.
305    fn clone(&self) -> Self {
306        Self {
307            stack: self.stack.clone(),
308            position: self.position,
309            max_depth: self.max_depth,
310            coalesce_window: self.coalesce_window,
311        }
312    }
313}
314
315impl std::fmt::Debug for UndoManager {
316    /// Debug format helper for UndoManager.
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        f.debug_struct("UndoManager")
319            .field("stack_len", &self.stack.len())
320            .field("position", &self.position)
321            .field("max_depth", &self.max_depth)
322            .field("coalesce_window", &self.coalesce_window)
323            .finish()
324    }
325}
326
327impl UndoManager {
328    /// Create a new UndoManager with custom settings.
329    pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
330        Self {
331            stack: Vec::new(),
332            position: 0,
333            max_depth,
334            coalesce_window,
335        }
336    }
337
338    /// Push a new undo/redo group to the stack, clearing any forward redo history.
339    pub fn push(
340        &mut self,
341        label: &str,
342        undo: impl Fn() + Send + Sync + 'static,
343        redo: impl Fn() + Send + Sync + 'static,
344    ) {
345        if self.position < self.stack.len() {
346            self.stack.truncate(self.position);
347        }
348
349        let timestamp = std::time::SystemTime::now()
350            .duration_since(std::time::UNIX_EPOCH)
351            .unwrap_or_default()
352            .as_secs_f32();
353
354        self.stack.push(UndoGroup {
355            label: label.to_string(),
356            timestamp,
357            undo: Arc::new(undo),
358            redo: Arc::new(redo),
359        });
360
361        if self.stack.len() > self.max_depth {
362            self.stack.remove(0);
363        }
364        self.position = self.stack.len();
365    }
366
367    /// Perform the undo action if possible, moving the position back.
368    /// Returns the undo closure to be executed outside of any state lock.
369    pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
370        if self.can_undo() {
371            self.position -= 1;
372            Some(Arc::clone(&self.stack[self.position].undo))
373        } else {
374            None
375        }
376    }
377
378    /// Perform the redo action if possible, moving the position forward.
379    /// Returns the redo closure to be executed outside of any state lock.
380    pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
381        if self.can_redo() {
382            let group = &self.stack[self.position];
383            self.position += 1;
384            Some(Arc::clone(&group.redo))
385        } else {
386            None
387        }
388    }
389
390    /// Returns true if there is an action that can be undone.
391    pub fn can_undo(&self) -> bool {
392        self.position > 0
393    }
394
395    /// Returns true if there is an action that can be redone.
396    pub fn can_redo(&self) -> bool {
397        self.position < self.stack.len()
398    }
399
400    /// Clear all undo/redo history.
401    pub fn clear(&mut self) {
402        self.stack.clear();
403        self.position = 0;
404    }
405
406    /// Push a new coalesceable action. If the last action in the stack matches the label,
407    /// is within the coalesce window, and the position is at the end of the stack, their undo/redo
408    /// functions will be combined instead of creating a new group.
409    pub fn push_coalesceable(
410        &mut self,
411        label: &str,
412        undo: impl Fn() + Send + Sync + 'static,
413        redo: impl Fn() + Send + Sync + 'static,
414    ) {
415        let now = std::time::SystemTime::now()
416            .duration_since(std::time::UNIX_EPOCH)
417            .unwrap_or_default()
418            .as_secs_f32();
419
420        if self.position == self.stack.len() && !self.stack.is_empty() {
421            let last_idx = self.stack.len() - 1;
422            let last = &self.stack[last_idx];
423            if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
424                let old_undo = Arc::clone(&last.undo);
425                let old_redo = Arc::clone(&last.redo);
426                let new_undo = Arc::new(undo);
427                let new_redo = Arc::new(redo);
428
429                self.stack[last_idx].undo = Arc::new(move || {
430                    new_undo();
431                    old_undo();
432                });
433                self.stack[last_idx].redo = Arc::new(move || {
434                    old_redo();
435                    new_redo();
436                });
437                self.stack[last_idx].timestamp = now;
438                return;
439            }
440        }
441
442        self.push(label, undo, redo);
443    }
444}
445
446/// Unique identifier for a window instance.
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
448pub struct WindowId(pub u64);
449
450/// Specifies the layering behavior of the window relative to other windows.
451#[derive(
452    Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
453)]
454pub enum WindowLevel {
455    /// Standard window.
456    #[default]
457    Normal,
458    /// Window stays above all standard windows.
459    AlwaysOnTop,
460    /// Menu or pop-up level window.
461    PopUpMenu,
462}
463
464/// Configuration settings for creating a new window.
465#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
466pub struct WindowConfig {
467    /// The window title bar text.
468    pub title: String,
469    /// Default width and height of the window.
470    pub size: (f32, f32),
471    /// Minimum allowed dimensions.
472    pub min_size: Option<(f32, f32)>,
473    /// Maximum allowed dimensions.
474    pub max_size: Option<(f32, f32)>,
475    /// Whether the window can be resized by the user.
476    pub resizable: bool,
477    /// Whether the window background is transparent.
478    pub transparent: bool,
479    /// Whether the window title bar and border decorations are drawn.
480    pub decorations: bool,
481    /// The window level layer.
482    pub level: WindowLevel,
483}
484
485impl Default for WindowConfig {
486    /// Create a standard default window configuration.
487    fn default() -> Self {
488        Self {
489            title: "CVKG Window".to_string(),
490            size: (800.0, 600.0),
491            min_size: None,
492            max_size: None,
493            resizable: true,
494            transparent: false,
495            decorations: true,
496            level: WindowLevel::Normal,
497        }
498    }
499}
500
501/// Abstract trait representing a platform-native window.
502/// Implementations delegate calls back to the platform renderers and events.
503pub trait Window: Send + Sync {
504    /// Request closing of the window.
505    fn close(&self);
506    /// Change the title bar text of the window.
507    fn set_title(&self, title: &str);
508    /// Update the window's physical dimensions.
509    fn set_size(&self, width: f32, height: f32);
510    /// Check if the window currently has keyboard focus.
511    fn is_key(&self) -> bool;
512    /// Check if this is the primary main application window.
513    fn is_main(&self) -> bool;
514    /// Check if the window is currently visible/mapped.
515    fn is_visible(&self) -> bool;
516    /// Hide or show the window.
517    fn set_visible(&self, visible: bool);
518    /// Bring the window to the front and focus it.
519    fn bring_to_front(&self);
520}
521
522/// A handle to a native window that can be used by application code.
523#[derive(Clone)]
524pub struct WindowHandle {
525    /// The unique identifier of this window.
526    pub id: WindowId,
527    /// Reference to the underlying platform window.
528    pub inner: Arc<dyn Window>,
529}
530
531impl std::fmt::Debug for WindowHandle {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        f.debug_struct("WindowHandle")
534            .field("id", &self.id)
535            .finish()
536    }
537}
538
539impl WindowHandle {
540    /// Create a new WindowHandle.
541    pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
542        Self { id, inner }
543    }
544    /// Request the window to close.
545    pub fn close(self) {
546        self.inner.close();
547    }
548    /// Set the title text of the window.
549    pub fn set_title(&self, title: &str) {
550        self.inner.set_title(title);
551    }
552    /// Resize the window.
553    pub fn set_size(&self, width: f32, height: f32) {
554        self.inner.set_size(width, height);
555    }
556    /// Returns true if this window has key focus.
557    pub fn is_key(&self) -> bool {
558        self.inner.is_key()
559    }
560    /// Returns true if this is the main application window.
561    pub fn is_main(&self) -> bool {
562        self.inner.is_main()
563    }
564    /// Returns true if the window is visible.
565    pub fn is_visible(&self) -> bool {
566        self.inner.is_visible()
567    }
568    /// Set visibility of the window.
569    pub fn set_visible(&self, visible: bool) {
570        self.inner.set_visible(visible);
571    }
572    /// Bring this window to the foreground.
573    pub fn bring_to_front(&self) {
574        self.inner.bring_to_front();
575    }
576}
577
578/// Action to take when a window close request event is received.
579#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
580pub enum WindowCloseAction {
581    /// Close the window immediately.
582    Allow,
583    /// Request confirmation from the user (e.g. show dialog).
584    Confirm,
585    /// Ignore the close request.
586    Deny,
587}
588
589#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
590pub struct AssetKey(pub String);
591
592impl EnvKey for AssetKey {
593    type Value = Arc<dyn AssetManager>;
594    fn default_value() -> Self::Value {
595        Arc::new(DefaultAssetManager::new())
596    }
597}
598
599/// Asset state for async resource loading.
600#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
601pub enum AssetState<T> {
602    Loading,
603    Ready(T),
604    Error(String),
605}
606
607/// Design token value that can adapt to light/dark mode
608#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(untagged)]
610pub enum TokenValue {
611    /// Single value (same for light and dark)
612    Single { value: String },
613    /// Different values for light and dark mode
614    Adaptive { light: String, dark: String },
615}
616
617/// YggdrasilTokens is the authoritative container for all design tokens in the CVKG ecosystem.
618#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct YggdrasilTokens {
620    pub color: HashMap<String, TokenValue>,
621    pub font: HashMap<String, TokenValue>,
622    pub spacing: HashMap<String, TokenValue>,
623    pub radius: HashMap<String, TokenValue>,
624    pub shadow: HashMap<String, TokenValue>,
625    pub border: HashMap<String, TokenValue>,
626    pub anim: HashMap<String, TokenValue>,
627    pub bifrost: HashMap<String, TokenValue>,
628    pub gungnir: HashMap<String, TokenValue>,
629    pub mjolnir: HashMap<String, TokenValue>,
630    pub accessibility: HashMap<String, TokenValue>,
631}
632
633impl Default for YggdrasilTokens {
634    fn default() -> Self {
635        Self::new()
636    }
637}
638
639impl YggdrasilTokens {
640    pub fn new() -> Self {
641        Self {
642            color: HashMap::new(),
643            font: HashMap::new(),
644            spacing: HashMap::new(),
645            radius: HashMap::new(),
646            shadow: HashMap::new(),
647            border: HashMap::new(),
648            anim: HashMap::new(),
649            bifrost: HashMap::new(),
650            gungnir: HashMap::new(),
651            mjolnir: HashMap::new(),
652            accessibility: HashMap::new(),
653        }
654    }
655
656    /// Get a color token value for the current mode
657    pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
658        self.color.get(key).map(|token| match token {
659            TokenValue::Single { value } => value.clone(),
660            TokenValue::Adaptive { light, dark } => {
661                if is_dark {
662                    dark.clone()
663                } else {
664                    light.clone()
665                }
666            }
667        })
668    }
669
670    /// Get a token value of any type and parse it into the target type
671    pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
672        let map = match category {
673            "color" => &self.color,
674            "font" => &self.font,
675            "spacing" => &self.spacing,
676            "radius" => &self.radius,
677            "shadow" => &self.shadow,
678            "border" => &self.border,
679            "anim" => &self.anim,
680            "bifrost" => &self.bifrost,
681            "gungnir" => &self.gungnir,
682            "mjolnir" => &self.mjolnir,
683            "accessibility" => &self.accessibility,
684            _ => return None,
685        };
686
687        map.get(key).and_then(|token| match token {
688            TokenValue::Single { value } => value.parse().ok(),
689            TokenValue::Adaptive { light, dark } => {
690                let value = if is_dark { dark } else { light };
691                value.parse().ok()
692            }
693        })
694    }
695}
696
697pub trait View: Sized + Send {
698    /// The concrete type produced after applying modifiers.
699    /// For primitive views this is Self.
700    type Body: View;
701
702    fn body(self) -> Self::Body;
703
704    /// Render this view into the provided renderer at the specified bounds.
705    /// Primitive views override this to perform drawing operations.
706    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
707
708    /// Calculate the natural (intrinsic) size of this view given proposed constraints.
709    /// This allows views like Buttons or Labels to inform the layout engine of their needs.
710    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
711        Size::ZERO
712    }
713
714    /// Optionally provide a layout implementation for this view.
715    fn layout(&self) -> Option<&dyn layout::LayoutView> {
716        None
717    }
718
719    /// Returns the flex weight of this view for proportional distribution in stacks.
720    fn flex_weight(&self) -> f32 {
721        0.0
722    }
723
724    /// Returns the grid placement configuration for this view if it is laid out in a Grid.
725    fn get_grid_placement(&self) -> Option<GridPlacement> {
726        None
727    }
728
729    /// Provided modifier entry point
730    fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
731        ModifiedView::new(self, m)
732    }
733
734    /// Apply a Bifrost (Frosted Glass) effect to the view
735    fn bifrost(
736        self,
737        blur: f32,
738        saturation: f32,
739        opacity: f32,
740    ) -> ModifiedView<Self, BifrostModifier> {
741        self.modifier(BifrostModifier {
742            blur,
743            saturation,
744            opacity,
745            fresnel_strength: 1.0,
746        })
747    }
748
749    /// Apply a Bifrost (Frosted Glass) effect with full parameter control.
750    fn bifrost_full(
751        self,
752        blur: f32,
753        saturation: f32,
754        opacity: f32,
755        fresnel_strength: f32,
756    ) -> ModifiedView<Self, BifrostModifier> {
757        self.modifier(BifrostModifier {
758            blur,
759            saturation,
760            opacity,
761            fresnel_strength,
762        })
763    }
764
765    /// Apply a Gungnir (Neon Glow) effect to the view
766    fn gungnir(
767        self,
768        color: impl Into<String>,
769        radius: f32,
770        intensity: f32,
771    ) -> ModifiedView<Self, GungnirModifier> {
772        self.modifier(GungnirModifier {
773            color: color.into(),
774            radius,
775            intensity,
776        })
777    }
778
779    /// Apply a Mjolnir Slice (Geometric cut) to the view
780    fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
781        self.modifier(MjolnirSliceModifier { angle, offset })
782    }
783
784    /// Apply a Mjolnir Shatter (Fragmented transition) to the view
785    fn mjolnir_shatter(
786        self,
787        pieces: u32,
788        force: f32,
789    ) -> ModifiedView<Self, MjolnirShatterModifier> {
790        self.modifier(MjolnirShatterModifier { pieces, force })
791    }
792
793    /// Mark this view as a Bifrost Bridge (Shared Element) for cross-view persistence
794    fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
795        self.modifier(BifrostBridgeModifier { id: id.into() })
796    }
797
798    /// Add a background color to this view
799    fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
800        self.modifier(BackgroundModifier { color })
801    }
802
803    /// Add padding to this view
804    fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
805        self.modifier(PaddingModifier { amount })
806    }
807
808    /// Set the opacity (alpha) of this view in the range [0.0, 1.0].
809    fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
810        self.modifier(OpacityModifier {
811            opacity: opacity.clamp(0.0, 1.0),
812        })
813    }
814
815    /// Override the foreground (text / icon) color of this view.
816    fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
817        self.modifier(ForegroundColorModifier { color })
818    }
819
820    /// Constrain this view to an explicit width and/or height.
821    /// Constrains the size of this view using fixed width/height values.
822    fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
823        self.modifier(FrameModifier {
824            width,
825            height,
826            min_width: None,
827            max_width: None,
828            min_height: None,
829            max_height: None,
830            alignment: Alignment::Center,
831        })
832    }
833
834    /// Give this view a flex weight for proportional space distribution in stacks.
835    fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
836        self.modifier(FlexModifier { weight })
837    }
838
839    /// Specify the grid placement configuration (column, row, column_span, row_span) for this view.
840    fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
841        self.modifier(GridPlacementModifier { placement })
842    }
843
844    /// Overlay a view on top of this view, aligned and offset relative to it.
845    fn overlay<O: View + Clone + 'static>(
846        self,
847        overlay: O,
848        alignment: Alignment,
849        offset: [f32; 2],
850        on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
851    ) -> ModifiedView<Self, OverlayModifier> {
852        self.modifier(OverlayModifier {
853            overlay: overlay.erase(),
854            alignment,
855            offset,
856            on_dismiss,
857        })
858    }
859
860    /// Automatically add padding to avoid overlapping with platform safe areas (notches, bars).
861    fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
862        self.modifier(SafeAreaModifier { ignores: false })
863    }
864
865    /// Explicitly ignore platform safe areas and draw into the margins.
866    fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
867        self.modifier(SafeAreaModifier { ignores: true })
868    }
869
870    /// Clip all child drawing to this view's bounds.
871    fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
872        self.modifier(ClipModifier)
873    }
874
875    /// Draw a colored border around this view.
876    fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
877        self.modifier(BorderModifier { color, width })
878    }
879
880    /// Add elevation (shadow) to the view. Level determines the shadow depth.
881    fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
882        self.modifier(ElevationModifier { level })
883    }
884
885    /// Add a magnetic effect that pulls the view towards the cursor.
886    fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
887        self.modifier(MagneticModifier { radius, intensity })
888    }
889
890    /// Add a ManiGlow (Lunar Illuminator) effect that glows near the cursor.
891    fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
892        self.modifier(ManiGlowModifier { color, radius })
893    }
894
895    /// Theme this view based on a specific memory layer.
896    fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
897        self.modifier(BifrostLayerModifier { layer })
898    }
899
900    /// Enable Fafnir's Evolution: The component grows and glows as it is used.
901    fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
902        self.modifier(FafnirModifier { id })
903    }
904
905    /// Enable Mimir's Intent: The component anticipates user interaction via pointer kinematics.
906    fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
907        self.modifier(MimirIntentModifier)
908    }
909
910    /// Enable Kvasir's Vibes: Subconscious telemetry representing cognitive complexity.
911    fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
912        self.modifier(KvasirVibeModifier { complexity })
913    }
914
915    /// Bestow Odin's Eye: Global omniscient observability layer.
916    fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
917        self.modifier(OdinsEyeModifier)
918    }
919
920    /// Trigger an action when the view appears
921    fn on_appear<F: Fn() + Send + Sync + 'static>(
922        self,
923        action: F,
924    ) -> ModifiedView<Self, LifecycleModifier> {
925        self.modifier(LifecycleModifier {
926            on_appear: Some(Arc::new(action)),
927            on_disappear: None,
928        })
929    }
930
931    /// Trigger an action when the view disappears
932    fn on_disappear<F: Fn() + Send + Sync + 'static>(
933        self,
934        action: F,
935    ) -> ModifiedView<Self, LifecycleModifier> {
936        self.modifier(LifecycleModifier {
937            on_appear: None,
938            on_disappear: Some(Arc::new(action)),
939        })
940    }
941
942    /// Trigger an action when the view is clicked
943    fn on_click<F: Fn() + Send + Sync + 'static>(
944        self,
945        action: F,
946    ) -> ModifiedView<Self, OnClickModifier> {
947        self.modifier(OnClickModifier {
948            action: Arc::new(action),
949        })
950    }
951
952    /// Trigger an action when the pointer enters the view bounds
953    fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
954        self,
955        action: F,
956    ) -> ModifiedView<Self, OnPointerEnterModifier> {
957        self.modifier(OnPointerEnterModifier {
958            action: Arc::new(action),
959        })
960    }
961
962    /// Trigger an action when the pointer leaves the view bounds
963    fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
964        self,
965        action: F,
966    ) -> ModifiedView<Self, OnPointerLeaveModifier> {
967        self.modifier(OnPointerLeaveModifier {
968            action: Arc::new(action),
969        })
970    }
971
972    /// Trigger an action when the pointer moves inside the view bounds
973    fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
974        self,
975        action: F,
976    ) -> ModifiedView<Self, OnPointerMoveModifier> {
977        self.modifier(OnPointerMoveModifier {
978            action: Arc::new(action),
979        })
980    }
981
982    /// Trigger an action when the pointer is pressed down
983    fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
984        self,
985        action: F,
986    ) -> ModifiedView<Self, OnPointerDownModifier> {
987        self.modifier(OnPointerDownModifier {
988            action: Arc::new(action),
989        })
990    }
991
992    /// Trigger an action when the pointer is released
993    fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
994        self,
995        action: F,
996    ) -> ModifiedView<Self, OnPointerUpModifier> {
997        self.modifier(OnPointerUpModifier {
998            action: Arc::new(action),
999        })
1000    }
1001
1002    /// Type-erase this view into AnyView
1003    fn erase(self) -> AnyView
1004    where
1005        Self: Clone + 'static,
1006    {
1007        AnyView::new(self)
1008    }
1009
1010    // =============================================================================
1011    // ACCESSIBILITY
1012    // =============================================================================
1013
1014    /// Return accessibility properties for this view.
1015    /// Override to expose semantic role, label, state to assistive technology.
1016    /// Default returns `None` (view is not explicitly accessible).
1017    fn aria_properties(&self) -> Option<AriaProperties> {
1018        None
1019    }
1020
1021    /// Handle a keyboard navigation event.
1022    /// Return true if consumed, false to bubble.
1023    fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
1024        false
1025    }
1026
1027    /// Return keyboard shortcuts this view responds to.
1028    fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1029        vec![]
1030    }
1031}
1032
1033// =============================================================================
1034// ARIA PROPERTIES
1035// =============================================================================
1036
1037/// Semantic role for assistive technology (WCAG 2.1 §4.1.2).
1038#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1039pub enum AriaRole {
1040    Alert,
1041    Alertdialog,
1042    Article,
1043    Banner,
1044    Button,
1045    Checkbox,
1046    Columnheader,
1047    Combobox,
1048    Complementary,
1049    Contentinfo,
1050    Dialog,
1051    Form,
1052    Grid,
1053    Gridcell,
1054    Heading,
1055    Img,
1056    Link,
1057    List,
1058    Listbox,
1059    Listitem,
1060    Main,
1061    Menu,
1062    Menubar,
1063    Menuitem,
1064    Menuitemcheckbox,
1065    Menuitemradio,
1066    Navigation,
1067    None,
1068    Note,
1069    Option,
1070    Presentation,
1071    Progressbar,
1072    Radio,
1073    Radiogroup,
1074    Region,
1075    Row,
1076    Rowgroup,
1077    Rowheader,
1078    Search,
1079    Separator,
1080    Slider,
1081    Spinbutton,
1082    Status,
1083    Switch,
1084    Tab,
1085    Table,
1086    Tablist,
1087    Tabpanel,
1088    Textbox,
1089    Toolbar,
1090    Tooltip,
1091    Tree,
1092    Treeitem,
1093}
1094
1095/// Accessible properties for a view, describing its semantic role and state.
1096#[derive(Debug, Clone, Serialize, Deserialize)]
1097pub struct AriaProperties {
1098    pub role: AriaRole,
1099    pub label: String,
1100    pub description: Option<String>,
1101    pub value: Option<String>,
1102    pub pressed: Option<bool>,
1103    pub checked: Option<bool>,
1104    pub expanded: Option<bool>,
1105    pub disabled: bool,
1106    pub hidden: bool,
1107    pub level: Option<u8>,
1108    pub shortcut: Option<String>,
1109    pub focused: bool,
1110    pub live: Option<String>,
1111    pub atomic: bool,
1112}
1113
1114impl AriaProperties {
1115    pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
1116        Self {
1117            role,
1118            label: label.into(),
1119            description: None,
1120            value: None,
1121            pressed: None,
1122            checked: None,
1123            expanded: None,
1124            disabled: false,
1125            hidden: false,
1126            level: None,
1127            shortcut: None,
1128            focused: false,
1129            live: None,
1130            atomic: false,
1131        }
1132    }
1133
1134    pub fn description(mut self, d: impl Into<String>) -> Self {
1135        self.description = Some(d.into());
1136        self
1137    }
1138    pub fn value(mut self, v: impl Into<String>) -> Self {
1139        self.value = Some(v.into());
1140        self
1141    }
1142    pub fn checked(mut self, c: bool) -> Self {
1143        self.checked = Some(c);
1144        self
1145    }
1146    pub fn disabled(mut self, d: bool) -> Self {
1147        self.disabled = d;
1148        self
1149    }
1150    pub fn expanded(mut self, e: bool) -> Self {
1151        self.expanded = Some(e);
1152        self
1153    }
1154    pub fn level(mut self, l: u8) -> Self {
1155        self.level = Some(l.clamp(1, 6));
1156        self
1157    }
1158    pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1159        self.shortcut = Some(s.into());
1160        self
1161    }
1162    pub fn focused(mut self, f: bool) -> Self {
1163        self.focused = f;
1164        self
1165    }
1166}
1167
1168// =============================================================================
1169// KEYBOARD NAVIGATION
1170// =============================================================================
1171
1172/// Modifier keys for keyboard events.
1173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1174pub struct KeyModifiers {
1175    pub shift: bool,
1176    pub ctrl: bool,
1177    pub alt: bool,
1178    pub meta: bool,
1179}
1180
1181/// A keyboard shortcut binding.
1182#[derive(Debug, Clone, Serialize, Deserialize)]
1183pub struct KeyShortcut {
1184    pub key: String,
1185    pub modifiers: KeyModifiers,
1186    pub description: String,
1187}
1188
1189impl KeyShortcut {
1190    pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
1191        Self {
1192            key: key.into(),
1193            modifiers: KeyModifiers::default(),
1194            description: desc.into(),
1195        }
1196    }
1197    pub fn with_ctrl(mut self) -> Self {
1198        self.modifiers.ctrl = true;
1199        self
1200    }
1201    pub fn with_shift(mut self) -> Self {
1202        self.modifiers.shift = true;
1203        self
1204    }
1205    pub fn with_alt(mut self) -> Self {
1206        self.modifiers.alt = true;
1207        self
1208    }
1209    pub fn with_meta(mut self) -> Self {
1210        self.modifiers.meta = true;
1211        self
1212    }
1213}
1214
1215// =============================================================================
1216// FOCUS MANAGEMENT
1217// =============================================================================
1218
1219/// Unique ID for a focusable element.
1220#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1221pub struct FocusableId(String);
1222
1223impl From<&str> for FocusableId {
1224    fn from(s: &str) -> Self {
1225        Self(s.to_string())
1226    }
1227}
1228impl From<String> for FocusableId {
1229    fn from(s: String) -> Self {
1230        Self(s)
1231    }
1232}
1233
1234/// Focus trap for confining Tab navigation (e.g., modals).
1235#[derive(Debug, Clone)]
1236pub struct FocusTrap {
1237    pub id: FocusableId,
1238    pub order: Vec<FocusableId>,
1239    pub wrap: bool,
1240}
1241
1242impl FocusTrap {
1243    pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
1244        Self {
1245            id: id.into(),
1246            order,
1247            wrap: true,
1248        }
1249    }
1250}
1251
1252/// Manages focus order, Tab/Shift+Tab navigation, and focus traps.
1253#[derive(Debug, Default)]
1254pub struct FocusManager {
1255    order: Vec<FocusableId>,
1256    focused: Option<FocusableId>,
1257    traps: Vec<FocusTrap>,
1258}
1259
1260impl FocusManager {
1261    pub fn new() -> Self {
1262        Self::default()
1263    }
1264
1265    pub fn register(&mut self, id: impl Into<FocusableId>) {
1266        let id = id.into();
1267        if !self.order.contains(&id) {
1268            self.order.push(id);
1269        }
1270    }
1271
1272    pub fn unregister(&mut self, id: &FocusableId) {
1273        self.order.retain(|x| x != id);
1274        if self.focused.as_ref() == Some(id) {
1275            self.focused = None;
1276        }
1277    }
1278
1279    pub fn focused(&self) -> Option<&FocusableId> {
1280        self.focused.as_ref()
1281    }
1282
1283    pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
1284        let id = id.into();
1285        if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
1286            self.focused = Some(id);
1287            true
1288        } else {
1289            false
1290        }
1291    }
1292
1293    pub fn focus_next(&mut self) -> Option<&FocusableId> {
1294        let order = self.effective_order();
1295        if order.is_empty() {
1296            return None;
1297        }
1298        let idx = self
1299            .focused
1300            .as_ref()
1301            .and_then(|f| order.iter().position(|x| x == f));
1302        let next = match idx {
1303            Some(i) if i + 1 < order.len() => &order[i + 1],
1304            _ => &order[0],
1305        };
1306        self.focused = Some(next.clone());
1307        self.focused.as_ref()
1308    }
1309
1310    pub fn focus_prev(&mut self) -> Option<&FocusableId> {
1311        let order = self.effective_order();
1312        if order.is_empty() {
1313            return None;
1314        }
1315        let idx = self
1316            .focused
1317            .as_ref()
1318            .and_then(|f| order.iter().position(|x| x == f));
1319        let prev = match idx {
1320            Some(i) if i > 0 => &order[i - 1],
1321            _ => &order[order.len() - 1],
1322        };
1323        self.focused = Some(prev.clone());
1324        self.focused.as_ref()
1325    }
1326
1327    pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
1328        let id = trap.id.clone();
1329        self.traps.push(trap);
1330        id
1331    }
1332
1333    pub fn pop_trap(&mut self) {
1334        self.traps.pop();
1335    }
1336    pub fn trap_count(&self) -> usize {
1337        self.traps.len()
1338    }
1339
1340    fn effective_order(&self) -> &[FocusableId] {
1341        self.traps
1342            .last()
1343            .map(|t| t.order.as_slice())
1344            .unwrap_or(&self.order)
1345    }
1346}
1347
1348// =============================================================================
1349// REDUCED MOTION
1350// =============================================================================
1351
1352/// Detects OS-level reduced motion preference.
1353pub fn is_reduced_motion() -> bool {
1354    std::env::var("GTK_THEME")
1355        .map(|v| v.to_lowercase().contains("reduced"))
1356        .unwrap_or(false)
1357        || std::env::var("NO_ANIMATIONS")
1358            .map(|v| v == "1" || v.to_lowercase() == "true")
1359            .unwrap_or(false)
1360        || std::env::var("ACCESSIBILITY_REDUCED_MOTION")
1361            .map(|v| v == "1" || v.to_lowercase() == "true")
1362            .unwrap_or(false)
1363}
1364
1365/// Returns effective animation duration (0.0 if reduced motion is active).
1366pub fn effective_duration(secs: f32) -> f32 {
1367    if is_reduced_motion() { 0.0 } else { secs }
1368}
1369
1370/// An object-safe version of the View trait for type erasure.
1371pub trait ErasedView: Send {
1372    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
1373    fn name(&self) -> &'static str;
1374    fn flex_weight_erased(&self) -> f32;
1375    fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
1376    fn grid_placement_erased(&self) -> Option<GridPlacement>;
1377    fn clone_box(&self) -> Box<dyn ErasedView>;
1378}
1379
1380impl<V: View + Clone + 'static> ErasedView for V {
1381    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
1382        self.render(renderer, rect);
1383    }
1384
1385    fn name(&self) -> &'static str {
1386        std::any::type_name::<V>()
1387    }
1388
1389    fn flex_weight_erased(&self) -> f32 {
1390        self.flex_weight()
1391    }
1392
1393    fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1394        self.layout()
1395    }
1396
1397    fn grid_placement_erased(&self) -> Option<GridPlacement> {
1398        self.get_grid_placement()
1399    }
1400
1401    fn clone_box(&self) -> Box<dyn ErasedView> {
1402        Box::new(self.clone())
1403    }
1404}
1405
1406/// A view that memoizes its rendering based on a stable ID and data hash.
1407/// The renderer can use this to skip re-rendering the sub-tree if the data hasn't changed.
1408pub struct MemoView<V, F> {
1409    id: u64,
1410    data_hash: u64,
1411    builder: F,
1412    _v: std::marker::PhantomData<V>,
1413}
1414
1415impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1416    /// Create a new MemoView with a stable ID and a data hash.
1417    pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1418        Self {
1419            id,
1420            data_hash,
1421            builder,
1422            _v: std::marker::PhantomData,
1423        }
1424    }
1425}
1426
1427impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1428    type Body = Never;
1429    fn body(self) -> Self::Body {
1430        unreachable!("MemoView does not have a body")
1431    }
1432
1433    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1434        renderer.memoize(self.id, self.data_hash, &|r| {
1435            let view = (self.builder)();
1436            view.render(r, rect);
1437        });
1438    }
1439}
1440
1441/// A type-erased View wrapper.
1442pub struct AnyView {
1443    inner: Box<dyn ErasedView>,
1444}
1445
1446impl Clone for AnyView {
1447    fn clone(&self) -> Self {
1448        Self {
1449            inner: self.inner.clone_box(),
1450        }
1451    }
1452}
1453
1454impl AnyView {
1455    pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1456        Self {
1457            inner: Box::new(view),
1458        }
1459    }
1460}
1461
1462impl View for AnyView {
1463    type Body = Never;
1464    fn body(self) -> Self::Body {
1465        unreachable!()
1466    }
1467
1468    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1469        renderer.push_vnode(rect, self.inner.name());
1470        self.inner.render_erased(renderer, rect);
1471        renderer.pop_vnode();
1472    }
1473
1474    fn flex_weight(&self) -> f32 {
1475        self.inner.flex_weight_erased()
1476    }
1477
1478    fn layout(&self) -> Option<&dyn layout::LayoutView> {
1479        self.inner.layout_erased()
1480    }
1481
1482    fn get_grid_placement(&self) -> Option<GridPlacement> {
1483        self.inner.grid_placement_erased()
1484    }
1485}
1486
1487/// BifrostBridgeModifier enables shared-element transitions.
1488/// When two views share the same Bifrost Bridge ID, the Sleipnir solver will
1489/// interpolate their geometry and effects (blur, glow) during the transition.
1490#[derive(Debug, Clone, PartialEq)]
1491pub struct BifrostBridgeModifier {
1492    pub id: String,
1493}
1494
1495impl ViewModifier for BifrostBridgeModifier {
1496    fn modify<V: View>(self, content: V) -> impl View {
1497        ModifiedView::new(content, self)
1498    }
1499
1500    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1501        // Register this element with the renderer for shared-element transition logic
1502        renderer.register_shared_element(&self.id, rect);
1503    }
1504}
1505
1506/// MjolnirSliceModifier implements the "Geometric Slice" aesthetic.
1507/// It uses a signed distance field (SDF) to clip the view along a sharp angled line.
1508#[derive(Debug, Clone, Copy, PartialEq)]
1509pub struct MjolnirSliceModifier {
1510    pub angle: f32,
1511    pub offset: f32,
1512}
1513
1514impl ViewModifier for MjolnirSliceModifier {
1515    fn modify<V: View>(self, content: V) -> impl View {
1516        ModifiedView::new(content, self)
1517    }
1518
1519    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1520        renderer.push_mjolnir_slice(self.angle, self.offset);
1521    }
1522
1523    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1524        renderer.pop_mjolnir_slice();
1525    }
1526}
1527
1528/// MjolnirShatterModifier implements the "Shattering" effect.
1529/// It breaks the view into discrete geometric fragments that can be animated.
1530#[derive(Debug, Clone, Copy, PartialEq)]
1531pub struct MjolnirShatterModifier {
1532    pub pieces: u32,
1533    pub force: f32,
1534}
1535
1536impl ViewModifier for MjolnirShatterModifier {
1537    fn modify<V: View>(self, content: V) -> impl View {
1538        ModifiedView::new(content, self)
1539    }
1540
1541    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1542        // RADIAL SHATTER: Fragment the view into wedges
1543        let pieces = self.pieces.max(1);
1544        for i in 0..pieces {
1545            let progress = i as f32 / pieces as f32;
1546            let next_progress = (i + 1) as f32 / pieces as f32;
1547
1548            let angle_start = progress * 360.0;
1549            let angle_end = next_progress * 360.0;
1550
1551            // Wedge slice: intersection of two half-planes
1552            renderer.push_mjolnir_slice(angle_start, 0.0);
1553            renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1554
1555            // Apply radial force offset
1556            let mid_angle = (angle_start + angle_end) / 2.0;
1557            let rad = mid_angle.to_radians();
1558            let dx = rad.cos() * self.force;
1559            let dy = rad.sin() * self.force;
1560
1561            let shard_rect = Rect {
1562                x: rect.x + dx,
1563                y: rect.y + dy,
1564                ..rect
1565            };
1566
1567            view.render(renderer, shard_rect);
1568
1569            renderer.pop_mjolnir_slice();
1570            renderer.pop_mjolnir_slice();
1571        }
1572    }
1573}
1574
1575/// BifrostModifier implements the Cyberpunk "Frosted Glass" aesthetic.
1576/// It triggers backdrop blurring and light scattering in the render pipeline.
1577#[derive(Debug, Clone, Copy, PartialEq)]
1578pub struct BifrostModifier {
1579    pub blur: f32,
1580    pub saturation: f32,
1581    pub opacity: f32,
1582    /// Fresnel strength multiplier. 0.0 = no fresnel, 1.0 = full.
1583    pub fresnel_strength: f32,
1584}
1585
1586impl ViewModifier for BifrostModifier {
1587    fn modify<V: View>(self, content: V) -> impl View {
1588        ModifiedView::new(content, self)
1589    }
1590
1591    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1592        if renderer.is_over_budget() {
1593            // Degrade: Use lower quality (half blur) if over budget
1594            renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1595        } else {
1596            renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1597        }
1598    }
1599}
1600
1601/// A modifier that adds a background color to a view.
1602#[derive(Debug, Clone, Copy, PartialEq)]
1603pub struct BackgroundModifier {
1604    pub color: [f32; 4],
1605}
1606
1607impl ViewModifier for BackgroundModifier {
1608    fn modify<V: View>(self, content: V) -> impl View {
1609        ModifiedView::new(content, self)
1610    }
1611
1612    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1613        renderer.fill_rect(rect, self.color);
1614    }
1615}
1616
1617/// A modifier that adds padding to a view.
1618#[derive(Debug, Clone, Copy, PartialEq)]
1619pub struct PaddingModifier {
1620    pub amount: f32,
1621}
1622
1623impl ViewModifier for PaddingModifier {
1624    fn modify<V: View>(self, content: V) -> impl View {
1625        ModifiedView::new(content, self)
1626    }
1627
1628    fn transform_rect(&self, rect: Rect) -> Rect {
1629        Rect {
1630            x: rect.x + self.amount,
1631            y: rect.y + self.amount,
1632            width: (rect.width - 2.0 * self.amount).max(0.0),
1633            height: (rect.height - 2.0 * self.amount).max(0.0),
1634        }
1635    }
1636
1637    fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1638        if let Some(w) = proposal.width {
1639            proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1640        }
1641        if let Some(h) = proposal.height {
1642            proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1643        }
1644        proposal
1645    }
1646
1647    fn transform_size(&self, mut size: Size) -> Size {
1648        size.width += 2.0 * self.amount;
1649        size.height += 2.0 * self.amount;
1650        size
1651    }
1652}
1653
1654/// GungnirModifier implements the "Neon Glow" aesthetic.
1655/// It uses additive blending and multi-pass blurring to simulate glowing light.
1656#[derive(Debug, Clone, PartialEq)]
1657pub struct GungnirModifier {
1658    pub color: String,
1659    pub radius: f32,
1660    pub intensity: f32,
1661}
1662
1663impl ViewModifier for GungnirModifier {
1664    fn modify<V: View>(self, content: V) -> impl View {
1665        ModifiedView::new(content, self)
1666    }
1667
1668    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1669        // Neon Glow using Mode 1 in the Surtr pipeline
1670        renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1671    }
1672}
1673
1674/// GungnirPulseModifier implements a "breathing" neon effect.
1675#[derive(Debug, Clone, Copy, PartialEq)]
1676pub struct GungnirPulseModifier {
1677    pub color: [f32; 4],
1678    pub radius: f32,
1679    pub speed: f32,
1680}
1681
1682impl ViewModifier for GungnirPulseModifier {
1683    fn modify<V: View>(self, content: V) -> impl View {
1684        ModifiedView::new(content, self)
1685    }
1686
1687    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1688        let time = std::time::SystemTime::now()
1689            .duration_since(std::time::UNIX_EPOCH)
1690            .unwrap_or_default()
1691            .as_secs_f32();
1692
1693        // Mode 19: Dashed Border
1694        // Mode 20: 9-Slice / Patch Scaling
1695        let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1696        let mut color = self.color;
1697        color[3] *= intensity;
1698
1699        // Mode 1 neon glow with dynamic intensity
1700        renderer.stroke_rect(rect, color, self.radius);
1701    }
1702}
1703
1704/// MagneticModifier makes a view "magnetic", subtly leaning towards or pulling the cursor.
1705#[derive(Debug, Clone, Copy, PartialEq)]
1706pub struct MagneticModifier {
1707    pub radius: f32,
1708    pub intensity: f32,
1709}
1710
1711impl ViewModifier for MagneticModifier {
1712    fn modify<V: View>(self, content: V) -> impl View {
1713        ModifiedView::new(content, self)
1714    }
1715
1716    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1717        let [px, py] = renderer.get_pointer_position();
1718        let center_x = rect.x + rect.width / 2.0;
1719        let center_y = rect.y + rect.height / 2.0;
1720
1721        let dx = px - center_x;
1722        let dy = py - center_y;
1723        let dist = (dx * dx + dy * dy).sqrt();
1724
1725        let mut offset_x = 0.0;
1726        let mut offset_y = 0.0;
1727
1728        if dist < self.radius && dist > 0.0 {
1729            let force = (1.0 - dist / self.radius) * self.intensity;
1730            offset_x = dx * force;
1731            offset_y = dy * force;
1732        }
1733
1734        let magnetic_rect = Rect {
1735            x: rect.x + offset_x,
1736            y: rect.y + offset_y,
1737            ..rect
1738        };
1739
1740        view.render(renderer, magnetic_rect);
1741    }
1742}
1743
1744/// ManiGlowModifier adds a soft, lunar-like cursor glow to a view.
1745/// Named after Máni, the personification of the Moon.
1746#[derive(Debug, Clone, Copy, PartialEq)]
1747pub struct ManiGlowModifier {
1748    pub color: [f32; 4],
1749    pub radius: f32,
1750}
1751
1752impl ViewModifier for ManiGlowModifier {
1753    fn modify<V: View>(self, content: V) -> impl View {
1754        ModifiedView::new(content, self)
1755    }
1756
1757    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1758        if crate::load_system_state().realm == Realm::Asgard {
1759            renderer.mani_glow(rect, self.color, self.radius);
1760        }
1761        view.render(renderer, rect);
1762    }
1763}
1764
1765/// BifrostLayerModifier themes a view based on its cognitive memory layer.
1766/// Episodic: Shifting aurora clouds.
1767/// Semantic: Crystalline gold.
1768/// Procedural: Heavy obsidian stone.
1769#[derive(Debug, Clone, Copy, PartialEq)]
1770pub struct BifrostLayerModifier {
1771    pub layer: MemoryLayer,
1772}
1773
1774impl ViewModifier for BifrostLayerModifier {
1775    fn modify<V: View>(self, content: V) -> impl View {
1776        ModifiedView::new(content, self)
1777    }
1778
1779    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1780        let realm = crate::load_system_state().realm;
1781        match self.layer {
1782            MemoryLayer::Episodic => {
1783                if realm == Realm::Asgard {
1784                    renderer.bifrost(rect, 40.0, 1.2, 0.7);
1785                } else {
1786                    renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1787                }
1788            }
1789            MemoryLayer::Semantic => {
1790                if realm == Realm::Asgard {
1791                    renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1792                } else {
1793                    renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1794                }
1795            }
1796            MemoryLayer::Procedural => {
1797                renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1798                let stroke_color = if realm == Realm::Asgard {
1799                    [0.3, 0.3, 0.3, 1.0]
1800                } else {
1801                    [0.2, 0.2, 0.2, 1.0]
1802                };
1803                renderer.stroke_rect(rect, stroke_color, 2.0);
1804            }
1805        }
1806        view.render(renderer, rect);
1807    }
1808}
1809
1810/// FafnirModifier enables self-evolving UI capabilities.
1811/// Named after Fafnir, the dragon who grows in power based on the gold he hoards.
1812/// In CVKG, 'Gold' is user attention/interaction.
1813#[derive(Debug, Clone, Copy, PartialEq)]
1814pub struct FafnirModifier {
1815    /// Unique ID for tracking this component's vitality across frames.
1816    pub id: u64,
1817}
1818
1819impl ViewModifier for FafnirModifier {
1820    fn modify<V: View>(self, content: V) -> impl View {
1821        ModifiedView::new(content, self)
1822    }
1823
1824    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1825        let state = crate::load_system_state();
1826        let vitality = state
1827            .get_component_state::<f32>(self.id)
1828            .map(|v| *v.read().unwrap())
1829            .unwrap_or(1.0);
1830
1831        // Calculate evolutionary growth factors
1832        // Max growth at vitality 5.0 (50% scale increase, strong glow)
1833        let growth = (vitality - 1.0).clamp(0.0, 4.0);
1834        let scale = 1.0 + growth * 0.12;
1835        let glow_intensity = growth * 0.25;
1836
1837        // Feed Fafnir: Register interaction to boost vitality
1838        let id = self.id;
1839        renderer.register_handler(
1840            "pointermove",
1841            std::sync::Arc::new(move |_| {
1842                crate::update_system_state(|s| {
1843                    let mut s = s.clone();
1844                    let v = s
1845                        .get_component_state::<f32>(id)
1846                        .map(|v| *v.read().unwrap())
1847                        .unwrap_or(1.0);
1848                    s.set_component_state(id, (v + 0.05).min(5.0)); // Cap at 5.0
1849                    s
1850                });
1851            }),
1852        );
1853
1854        if scale > 1.01 {
1855            renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1856        }
1857
1858        if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1859            renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1860        }
1861
1862        view.render(renderer, rect);
1863
1864        if scale > 1.01 {
1865            renderer.pop_transform();
1866        }
1867    }
1868}
1869
1870/// MimirIntentModifier anticipates user movement and manifests holographic ghosts.
1871#[derive(Debug, Clone, Copy, PartialEq)]
1872pub struct MimirIntentModifier;
1873
1874impl ViewModifier for MimirIntentModifier {
1875    fn modify<V: View>(self, content: V) -> impl View {
1876        ModifiedView::new(content, self)
1877    }
1878
1879    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1880        let state = crate::load_system_state();
1881        let pos = state.last_pointer_pos;
1882        let vel = state.pointer_velocity;
1883
1884        // Calculate if the cursor is moving towards this rect
1885        let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1886        let dx = center[0] - pos[0];
1887        let dy = center[1] - pos[1];
1888
1889        // Dot product of velocity and direction to center
1890        let dot = vel[0] * dx + vel[1] * dy;
1891        let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1892        let dist_sq = dx * dx + dy * dy;
1893
1894        if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1895            // Intent detected: render a subtle "ghost" reveal
1896            let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1897            renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1898        }
1899
1900        view.render(renderer, rect);
1901    }
1902}
1903
1904/// KvasirVibeModifier renders a cognitive telemetry cloud representing agent complexity.
1905#[derive(Debug, Clone, Copy, PartialEq)]
1906pub struct KvasirVibeModifier {
1907    pub complexity: f32,
1908}
1909
1910impl ViewModifier for KvasirVibeModifier {
1911    fn modify<V: View>(self, content: V) -> impl View {
1912        ModifiedView::new(content, self)
1913    }
1914
1915    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1916        if crate::load_system_state().realm == Realm::Asgard {
1917            let t = renderer.elapsed_time();
1918            let c = self.complexity.clamp(0.0, 1.0);
1919
1920            // 1. Core Cognitive Cloud (Bifrost)
1921            // Turbulence increases with complexity
1922            let blur = 20.0 + c * 40.0;
1923            let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1924            let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1925            renderer.bifrost(
1926                rect.offset(turbulence_x, turbulence_y),
1927                blur,
1928                0.8 + c * 0.4,
1929                0.25,
1930            );
1931
1932            // 2. Synaptic Discharge (Gungnir pulses)
1933            if c > 0.2 {
1934                let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1935                let color = [0.0, 0.9, 1.0, 0.4 * pulse]; // Cyan synaptic pulse
1936                renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1937            }
1938
1939            // 3. Unstable Resonance (Magenta/Red shift for high complexity)
1940            if c > 0.7 {
1941                let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1942                let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1943                renderer.fill_rect(rect, warning_color);
1944                renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1945            }
1946        }
1947        view.render(renderer, rect);
1948    }
1949}
1950
1951/// OdinsEyeModifier bestows omniscient observability over the entire scene graph.
1952#[derive(Debug, Clone, Copy, PartialEq)]
1953pub struct OdinsEyeModifier;
1954
1955impl ViewModifier for OdinsEyeModifier {
1956    fn modify<V: View>(self, content: V) -> impl View {
1957        ModifiedView::new(content, self)
1958    }
1959
1960    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1961        let state = crate::load_system_state();
1962        let t = renderer.elapsed_time();
1963
1964        // 1. Render Background content
1965        view.render(renderer, rect);
1966
1967        if state.realm == Realm::Asgard {
1968            // 2. Bestow Odin's Eye (Atmospheric Overlay)
1969            // Soft, large circular pulse representing the 'Eye'
1970            let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1971            renderer.draw_radial_gradient(
1972                rect,
1973                [0.0, 0.6, 0.8, 0.08 + eye_pulse], // Inner Cyan
1974                [0.0, 0.0, 0.0, 0.0],              // Outer Black
1975            );
1976
1977            // 3. Hugin (Thought) Telemetry - Left Side
1978            let hugin_rect = Rect {
1979                x: rect.x + 20.0,
1980                y: rect.y + 40.0,
1981                width: 200.0,
1982                height: rect.height - 80.0,
1983            };
1984            renderer.draw_text(
1985                "HUGIN: THOUGHT",
1986                hugin_rect.x,
1987                hugin_rect.y,
1988                10.0,
1989                [0.0, 1.0, 1.0, 0.6],
1990            );
1991            for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1992                renderer.draw_text(
1993                    thought,
1994                    hugin_rect.x,
1995                    hugin_rect.y + 20.0 + i as f32 * 14.0,
1996                    9.0,
1997                    [1.0, 1.0, 1.0, 0.4],
1998                );
1999            }
2000
2001            // 4. Munin (Memory) Telemetry - Right Side
2002            let munin_rect = Rect {
2003                x: rect.x + rect.width - 220.0,
2004                y: rect.y + 40.0,
2005                width: 200.0,
2006                height: rect.height - 80.0,
2007            };
2008            renderer.draw_text(
2009                "MUNIN: MEMORY",
2010                munin_rect.x,
2011                munin_rect.y,
2012                10.0,
2013                [1.0, 0.84, 0.0, 0.6],
2014            );
2015            for (i, node) in state.nodes.iter().take(10).enumerate() {
2016                let opacity = (node.weight.min(1.0)) * 0.5;
2017                renderer.draw_text(
2018                    &node.id,
2019                    munin_rect.x,
2020                    munin_rect.y + 20.0 + i as f32 * 14.0,
2021                    9.0,
2022                    [1.0, 1.0, 1.0, opacity],
2023                );
2024            }
2025
2026            // 5. Omniscient Focus Beams (Gungnir Beams)
2027            if let Some(focus_id) = &state.odin_focus {
2028                // Visualize causal links to the focus node
2029                renderer.draw_text(
2030                    &format!("EYE FOCUS: {}", focus_id),
2031                    rect.x + rect.width / 2.0 - 50.0,
2032                    rect.y + 20.0,
2033                    12.0,
2034                    [0.0, 1.0, 1.0, 0.8],
2035                );
2036
2037                // In a real implementation, we would find the rect of the focus_id component.
2038                // For the 'Eye', we manifest a central beam of wisdom.
2039                renderer.gungnir(
2040                    Rect {
2041                        x: rect.x + rect.width / 2.0 - 1.0,
2042                        y: rect.y,
2043                        width: 2.0,
2044                        height: rect.height,
2045                    },
2046                    [0.0, 1.0, 1.0, 1.0],
2047                    20.0,
2048                    0.4,
2049                );
2050            }
2051        }
2052    }
2053}
2054
2055/// Sleipnir spring parameters for the physics solver
2056#[derive(Debug, Clone, Copy, PartialEq)]
2057pub struct SleipnirParams {
2058    pub stiffness: f32,
2059    pub damping: f32,
2060    pub mass: f32,
2061}
2062
2063impl SleipnirParams {
2064    pub fn snappy() -> Self {
2065        Self {
2066            stiffness: 230.0,
2067            damping: 22.0,
2068            mass: 1.0,
2069        }
2070    }
2071    pub fn fluid() -> Self {
2072        Self {
2073            stiffness: 170.0,
2074            damping: 26.0,
2075            mass: 1.0,
2076        }
2077    }
2078    pub fn heavy() -> Self {
2079        Self {
2080            stiffness: 90.0,
2081            damping: 20.0,
2082            mass: 1.0,
2083        }
2084    }
2085    pub fn bouncy() -> Self {
2086        Self {
2087            stiffness: 190.0,
2088            damping: 14.0,
2089            mass: 1.0,
2090        }
2091    }
2092}
2093
2094impl Default for SleipnirParams {
2095    fn default() -> Self {
2096        Self::fluid()
2097    }
2098}
2099
2100#[derive(Debug, Clone, Copy, PartialEq)]
2101struct SolverState {
2102    x: f32,
2103    v: f32,
2104}
2105
2106/// SleipnirSolver implements a 4th-order Runge-Kutta (RK4) integration for springs.
2107/// This provides superior stability for high-fidelity interactive motion.
2108#[derive(Debug, Clone, Copy, PartialEq)]
2109pub struct SleipnirSolver {
2110    params: SleipnirParams,
2111    target: f32,
2112    state: SolverState,
2113}
2114
2115impl SleipnirSolver {
2116    /// Create a new solver with a target value and starting state.
2117    pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
2118        Self {
2119            params,
2120            target,
2121            state: SolverState { x: current, v: 0.0 },
2122        }
2123    }
2124
2125    /// Advance the simulation by dt seconds using RK4 integration.
2126    pub fn tick(&mut self, dt: f32) -> f32 {
2127        if dt <= 0.0 {
2128            return self.state.x;
2129        }
2130
2131        // Use a fixed time step for stability if dt is too large
2132        let mut remaining = dt;
2133        let step = 1.0 / 120.0;
2134
2135        while remaining > 0.0 {
2136            let d = remaining.min(step);
2137            self.step(d);
2138            remaining -= d;
2139        }
2140
2141        self.state.x
2142    }
2143
2144    fn step(&mut self, dt: f32) {
2145        let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
2146        let b = self.evaluate(self.state, dt * 0.5, a);
2147        let c = self.evaluate(self.state, dt * 0.5, b);
2148        let d = self.evaluate(self.state, dt, c);
2149
2150        let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
2151        let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
2152
2153        self.state.x += dxdt * dt;
2154        self.state.v += dvdt * dt;
2155    }
2156
2157    fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
2158        let state = SolverState {
2159            x: initial.x + d.x * dt,
2160            v: initial.v + d.v * dt,
2161        };
2162        let force =
2163            -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
2164        let mass = self.params.mass.max(0.001);
2165        SolverState {
2166            x: state.v,
2167            v: force / mass,
2168        }
2169    }
2170
2171    pub fn is_settled(&self) -> bool {
2172        (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
2173    }
2174
2175    pub fn set_target(&mut self, target: f32) {
2176        self.target = target;
2177    }
2178
2179    pub fn current_value(&self) -> f32 {
2180        self.state.x
2181    }
2182}
2183
2184/// SleipnirModifier handles physics-based animations via the Sleipnir RK4 solver.
2185#[derive(Debug, Clone, PartialEq)]
2186pub struct SleipnirModifier {
2187    pub id: u64,
2188    pub target: f32,
2189    pub params: SleipnirParams,
2190}
2191
2192impl ViewModifier for SleipnirModifier {
2193    fn modify<V: View>(self, content: V) -> impl View {
2194        ModifiedView::new(content, self)
2195    }
2196
2197    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2198        let state = load_system_state();
2199
2200        // Try to fetch the solver from persistent state.
2201        let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
2202
2203        let current_val;
2204
2205        if let Some(lock) = solver_lock_opt {
2206            // Found a solver. Tick it.
2207            let mut solver = lock.write().unwrap();
2208            solver.set_target(self.target);
2209            current_val = solver.tick(renderer.delta_time());
2210
2211            // If the solver hasn't settled yet, request another frame.
2212            if !solver.is_settled() {
2213                renderer.request_redraw();
2214            }
2215        } else {
2216            // First time seeing this ID. Initialize solver state.
2217            let solver = SleipnirSolver::new(
2218                self.params,
2219                self.target,
2220                self.target, // Initialize at target to avoid jump on first frame
2221            );
2222
2223            // Insert into registry for next frame.
2224            get_system_state().rcu(|old| {
2225                let mut new_state = (**old).clone();
2226                new_state.set_component_state(self.id, solver);
2227                new_state
2228            });
2229
2230            current_val = self.target;
2231        }
2232
2233        // Apply the solved value as a vertical translation.
2234        renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
2235        view.render(renderer, rect);
2236        renderer.pop_transform();
2237    }
2238}
2239
2240/// TransformModifier applies a 2D transform (translation, scale, rotation) to its child.
2241/// This modifier is "layout-neutral" and can be animated without re-running the layout engine.
2242#[derive(Debug, Clone, Copy, PartialEq)]
2243pub struct TransformModifier {
2244    pub translation: [f32; 2],
2245    pub scale: [f32; 2],
2246    pub rotation: f32,
2247}
2248
2249impl Default for TransformModifier {
2250    fn default() -> Self {
2251        Self::new()
2252    }
2253}
2254
2255impl TransformModifier {
2256    pub fn new() -> Self {
2257        Self {
2258            translation: [0.0, 0.0],
2259            scale: [1.0, 1.0],
2260            rotation: 0.0,
2261        }
2262    }
2263
2264    pub fn translate(mut self, x: f32, y: f32) -> Self {
2265        self.translation = [x, y];
2266        self
2267    }
2268
2269    pub fn scale(mut self, x: f32, y: f32) -> Self {
2270        self.scale = [x, y];
2271        self
2272    }
2273
2274    pub fn rotate(mut self, radians: f32) -> Self {
2275        self.rotation = radians;
2276        self
2277    }
2278}
2279
2280impl ViewModifier for TransformModifier {
2281    fn modify<V: View>(self, content: V) -> impl View {
2282        ModifiedView::new(content, self)
2283    }
2284
2285    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2286        renderer.push_transform(self.translation, self.scale, self.rotation);
2287        view.render(renderer, rect);
2288        renderer.pop_transform();
2289    }
2290}
2291
2292/// LifecycleModifier handles on_appear and on_disappear hooks.
2293
2294#[derive(Clone)]
2295pub struct LifecycleModifier {
2296    pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
2297    pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
2298}
2299
2300impl ViewModifier for LifecycleModifier {
2301    fn modify<V: View>(self, content: V) -> impl View {
2302        ModifiedView::new(content, self)
2303    }
2304}
2305
2306/// OpacityModifier fades this view and all its descendants to the given alpha.
2307/// The renderer is expected to honour `push_opacity`/`pop_opacity` on the Renderer trait.
2308#[derive(Debug, Clone, Copy, PartialEq)]
2309pub struct OpacityModifier {
2310    pub opacity: f32,
2311}
2312
2313impl ViewModifier for OpacityModifier {
2314    fn modify<V: View>(self, content: V) -> impl View {
2315        ModifiedView::new(content, self)
2316    }
2317
2318    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2319        renderer.push_opacity(self.opacity);
2320    }
2321
2322    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2323        renderer.pop_opacity();
2324    }
2325}
2326
2327/// OnClickModifier registers a click handler for this view.
2328#[derive(Clone)]
2329pub struct OnClickModifier {
2330    pub action: Arc<dyn Fn() + Send + Sync>,
2331}
2332
2333impl ViewModifier for OnClickModifier {
2334    fn modify<V: View>(self, content: V) -> impl View {
2335        ModifiedView::new(content, self)
2336    }
2337
2338    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2339        let action = self.action.clone();
2340        renderer.register_handler(
2341            "pointerclick",
2342            std::sync::Arc::new(move |event| {
2343                if let Event::PointerClick { .. } = event {
2344                    (action)();
2345                }
2346            }),
2347        );
2348    }
2349}
2350
2351/// OnPointerEnterModifier registers a pointer enter handler.
2352#[derive(Clone)]
2353pub struct OnPointerEnterModifier {
2354    pub action: Arc<dyn Fn() + Send + Sync>,
2355}
2356
2357impl ViewModifier for OnPointerEnterModifier {
2358    fn modify<V: View>(self, content: V) -> impl View {
2359        ModifiedView::new(content, self)
2360    }
2361
2362    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2363        let action = self.action.clone();
2364        renderer.register_handler(
2365            "pointerenter",
2366            std::sync::Arc::new(move |event| {
2367                if let Event::PointerEnter = event {
2368                    (action)();
2369                }
2370            }),
2371        );
2372    }
2373}
2374
2375/// OnPointerLeaveModifier registers a pointer leave handler.
2376#[derive(Clone)]
2377pub struct OnPointerLeaveModifier {
2378    pub action: Arc<dyn Fn() + Send + Sync>,
2379}
2380
2381impl ViewModifier for OnPointerLeaveModifier {
2382    fn modify<V: View>(self, content: V) -> impl View {
2383        ModifiedView::new(content, self)
2384    }
2385
2386    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2387        let action = self.action.clone();
2388        renderer.register_handler(
2389            "pointerleave",
2390            std::sync::Arc::new(move |event| {
2391                if let Event::PointerLeave = event {
2392                    (action)();
2393                }
2394            }),
2395        );
2396    }
2397}
2398
2399/// OnPointerMoveModifier registers a pointer move handler.
2400#[derive(Clone)]
2401pub struct OnPointerMoveModifier {
2402    pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2403}
2404
2405impl ViewModifier for OnPointerMoveModifier {
2406    fn modify<V: View>(self, content: V) -> impl View {
2407        ModifiedView::new(content, self)
2408    }
2409
2410    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2411        let action = self.action.clone();
2412        renderer.register_handler(
2413            "pointermove",
2414            std::sync::Arc::new(move |event| {
2415                if let Event::PointerMove { x, y, .. } = event {
2416                    (action)(x, y);
2417                }
2418            }),
2419        );
2420    }
2421}
2422
2423/// OnPointerDownModifier registers a pointer down handler.
2424#[derive(Clone)]
2425pub struct OnPointerDownModifier {
2426    pub action: Arc<dyn Fn() + Send + Sync>,
2427}
2428
2429impl ViewModifier for OnPointerDownModifier {
2430    fn modify<V: View>(self, content: V) -> impl View {
2431        ModifiedView::new(content, self)
2432    }
2433
2434    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2435        let action = self.action.clone();
2436        renderer.register_handler(
2437            "pointerdown",
2438            std::sync::Arc::new(move |event| {
2439                if let Event::PointerDown { .. } = event {
2440                    (action)();
2441                }
2442            }),
2443        );
2444    }
2445}
2446
2447/// OnPointerUpModifier registers a pointer up handler.
2448#[derive(Clone)]
2449pub struct OnPointerUpModifier {
2450    pub action: Arc<dyn Fn() + Send + Sync>,
2451}
2452
2453impl ViewModifier for OnPointerUpModifier {
2454    fn modify<V: View>(self, content: V) -> impl View {
2455        ModifiedView::new(content, self)
2456    }
2457
2458    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2459        let action = self.action.clone();
2460        renderer.register_handler(
2461            "pointerup",
2462            std::sync::Arc::new(move |event| {
2463                if let Event::PointerUp { .. } = event {
2464                    (action)();
2465                }
2466            }),
2467        );
2468    }
2469}
2470
2471/// ForegroundColorModifier overrides the foreground (text / icon) color inherited
2472/// by all descendants until another ForegroundColorModifier is encountered.
2473#[derive(Debug, Clone, Copy, PartialEq)]
2474pub struct ForegroundColorModifier {
2475    pub color: [f32; 4],
2476}
2477
2478impl ViewModifier for ForegroundColorModifier {
2479    fn modify<V: View>(self, content: V) -> impl View {
2480        ModifiedView::new(content, self)
2481    }
2482}
2483
2484/// ClipModifier restricts all child drawing to the view's layout rectangle.
2485/// The renderer must support `push_clip_rect`/`pop_clip_rect`.
2486#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2487pub struct ClipModifier;
2488
2489impl ViewModifier for ClipModifier {
2490    fn modify<V: View>(self, content: V) -> impl View {
2491        ModifiedView::new(content, self)
2492    }
2493
2494    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2495        renderer.push_clip_rect(rect);
2496    }
2497
2498    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2499        renderer.pop_clip_rect();
2500    }
2501}
2502
2503/// BorderModifier draws a solid-color border around the view bounds.
2504#[derive(Debug, Clone, Copy, PartialEq)]
2505pub struct BorderModifier {
2506    pub color: [f32; 4],
2507    pub width: f32,
2508}
2509
2510impl ViewModifier for BorderModifier {
2511    fn modify<V: View>(self, content: V) -> impl View {
2512        ModifiedView::new(content, self)
2513    }
2514
2515    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2516        renderer.stroke_rect(rect, self.color, self.width);
2517    }
2518}
2519
2520// Primitive (leaf) views implement Never as body
2521#[doc(hidden)]
2522pub enum Never {}
2523
2524impl View for Never {
2525    type Body = Never;
2526    fn body(self) -> Never {
2527        unreachable!()
2528    }
2529}
2530
2531/// EmptyView - A view that renders nothing and takes up no space.
2532#[derive(Debug, Clone, Copy, Default)]
2533pub struct EmptyView;
2534
2535impl View for EmptyView {
2536    type Body = Never;
2537    fn body(self) -> Self::Body {
2538        unreachable!()
2539    }
2540    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2541    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2542        Size {
2543            width: 0.0,
2544            height: 0.0,
2545        }
2546    }
2547}
2548
2549/// A view that has been transformed by a modifier.
2550/// Section 4.3: "Each modifier implements ViewModifier and produces a ModifiedView<Inner, Self>."
2551#[derive(Clone)]
2552pub struct ModifiedView<V, M> {
2553    view: V,
2554    modifier: M,
2555}
2556
2557impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2558    #[doc(hidden)]
2559    pub fn new(view: V, modifier: M) -> Self {
2560        Self { view, modifier }
2561    }
2562}
2563
2564impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2565    type Body = ModifiedView<V::Body, M>;
2566
2567    fn body(self) -> Self::Body {
2568        ModifiedView {
2569            view: self.view.body(),
2570            modifier: self.modifier.clone(),
2571        }
2572    }
2573
2574    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2575        self.modifier.render_view(&self.view, renderer, rect);
2576    }
2577
2578    fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2579        self.modifier.measure_view(&self.view, renderer, proposal)
2580    }
2581
2582    fn flex_weight(&self) -> f32 {
2583        self.modifier.child_flex_weight(&self.view)
2584    }
2585
2586    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2587        self.modifier.layout().or_else(|| self.view.layout())
2588    }
2589
2590    fn get_grid_placement(&self) -> Option<GridPlacement> {
2591        self.modifier
2592            .get_grid_placement()
2593            .or_else(|| self.view.get_grid_placement())
2594    }
2595}
2596
2597pub trait ViewModifier: Send + Clone {
2598    fn modify<V: View>(self, content: V) -> impl View;
2599
2600    /// Returns the grid placement configuration if this modifier defines one.
2601    fn get_grid_placement(&self) -> Option<GridPlacement> {
2602        None
2603    }
2604
2605    /// Core rendering hook called before child views.
2606    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2607
2608    /// Cleanup hook called after child views.
2609    fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2610
2611    /// Allows a modifier to completely override or wrap the rendering of its child.
2612    /// Default implementation performs a standard push -> transform -> render child -> pop sequence.
2613    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2614        self.render(renderer, rect);
2615        let child_rect = self.transform_rect(rect);
2616        view.render(renderer, child_rect);
2617        self.post_render(renderer, rect);
2618    }
2619
2620    fn transform_rect(&self, rect: Rect) -> Rect {
2621        rect
2622    }
2623
2624    /// Allows a modifier to transform the layout proposal before it reaches the child.
2625    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2626        proposal
2627    }
2628
2629    /// Allows a modifier to transform the resulting size from the child.
2630    fn transform_size(&self, size: Size) -> Size {
2631        size
2632    }
2633
2634    /// Measure hook that coordinates size propagation.
2635    fn measure_view<V: View>(
2636        &self,
2637        view: &V,
2638        renderer: &mut dyn Renderer,
2639        proposal: SizeProposal,
2640    ) -> Size {
2641        let child_proposal = self.transform_proposal(proposal);
2642        let child_size = view.intrinsic_size(renderer, child_proposal);
2643        self.transform_size(child_size)
2644    }
2645
2646    /// Allows a modifier to override or pass through the child's flex weight.
2647    fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2648        view.flex_weight()
2649    }
2650
2651    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2652        None
2653    }
2654}
2655
2656/// TelemetryData tracks real-time performance metrics for the GPU renderer.
2657#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2658pub struct TelemetryData {
2659    pub frame_time_ms: f32,
2660    /// 99th percentile frame time over the last window, used to detect tail latency.
2661    pub p99_frame_time_ms: f32,
2662    /// Statistical jitter (variance in frame timing).
2663    pub frame_jitter_ms: f32,
2664    /// Indicates if a hardware stall (DRAM refresh, thermal spike) was detected.
2665    pub hardware_stall_detected: bool,
2666
2667    // Pass timing
2668    pub input_time_ms: f32,
2669    pub state_flush_time_ms: f32,
2670    pub layout_time_ms: f32,
2671    pub draw_time_ms: f32,
2672    pub gpu_submit_time_ms: f32,
2673
2674    pub draw_calls: u32,
2675    pub vertices: u32,
2676
2677    /// Global Berserker Pipeline Intensity (0.0 - 1.0+)
2678    pub berserker_rage: f32,
2679
2680    // Memory breakdown
2681    pub vram_usage_mb: f32,
2682    pub vram_textures_mb: f32,
2683    pub vram_buffers_mb: f32,
2684    pub vram_pipelines_mb: f32,
2685    /// Indicates if the Mega-Atlas or VRAM pools are at capacity.
2686    pub vram_exhausted: bool,
2687}
2688
2689/// Configuration for render-loop frame timing and degradation strategies.
2690#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2691pub struct FrameBudget {
2692    /// Target frame time in milliseconds (default: 16.0 for 60FPS)
2693    pub target_ms: f32,
2694    /// If true, the renderer is allowed to dynamically skip non-critical effects
2695    /// (like heavy blurs or complex shadows) when the budget is exceeded.
2696    pub allow_degradation: bool,
2697}
2698
2699impl Default for FrameBudget {
2700    fn default() -> Self {
2701        Self {
2702            target_ms: 16.0,
2703            allow_degradation: true,
2704        }
2705    }
2706}
2707
2708/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2709/// This trait is object-safe and used by the View::render system.
2710/// # Implementation Requirements
2711/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2712/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2713/// 3. All operations must be batchable by the underlying backend.
2714///    Trait providing timing information for the render loop.
2715pub trait ElapsedTime {
2716    /// Returns the cumulative time since the renderer started in seconds.
2717    fn elapsed_time(&self) -> f32;
2718
2719    /// Returns the time elapsed since the last frame in seconds.
2720    fn delta_time(&self) -> f32;
2721}
2722
2723/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2724/// This trait is object-safe and used by the View::render system.
2725/// # Implementation Requirements
2726/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2727/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2728/// 3. All operations must be batchable by the underlying backend.
2729pub trait Renderer: ElapsedTime + Send {
2730    /// Requests that the renderer redraws as soon as possible.
2731    /// Used for continuous animations.
2732    fn request_redraw(&mut self) {}
2733
2734    /// Returns true if the current frame is over the time budget.
2735    /// This can be used to skip expensive visual effects.
2736    fn is_over_budget(&self) -> bool {
2737        false
2738    }
2739
2740    // ── Filled shapes ────────────────────────────────────────────────────
2741    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2742    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2743    /// Fill an ellipse/circle that fits inside `rect`.
2744    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2745
2746    /// Fill a rounded rect with glass material for frosted backdrop effect.
2747    /// This is the proper way to render glass cards for macOS Tahoe-style blur.
2748    /// The blur_radius controls the intensity of the backdrop blur.
2749    fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
2750        // Default no-op implementation; GPU backend overrides
2751        let _ = (rect, radius, blur_radius);
2752    }
2753
2754    /// Draw a high-fidelity 3D cube inside the given rectangle using specialized shader logic.
2755    /// `rotation` is [pitch, yaw, roll] in radians.
2756    fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2757
2758    // ── Stroked shapes ───────────────────────────────────────────────────
2759    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2760    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2761    /// Stroke an ellipse/circle that fits inside `rect`.
2762    fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2763    /// Draw a straight line from (x1,y1) to (x2,y2).
2764    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2765    /// Fill a polygon defined by a set of vertices.
2766    fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2767    /// Stroke a polygon defined by a set of vertices.
2768    fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2769
2770    // ── Text ─────────────────────────────────────────────────────────────
2771    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2772    /// Measure the width and height of the specified text.
2773    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2774    /// Return the baseline offset (ascent) for the given text and size.
2775    /// This is the distance from the text origin (y in draw_text) to the baseline.
2776    /// Default returns 0.0; override in renderers that support text shaping.
2777    fn measure_text_baseline(&mut self, _text: &str, _size: f32) -> f32 {
2778        0.0
2779    }
2780
2781    fn shape_rich_text(
2782        &mut self,
2783        _spans: &[cvkg_runic_text::TextSpan],
2784        _max_width: Option<f32>,
2785        _align: cvkg_runic_text::TextAlign,
2786        _overflow: cvkg_runic_text::TextOverflow,
2787    ) -> Option<cvkg_runic_text::ShapedText> {
2788        None
2789    }
2790
2791    fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2792
2793    // ── Images & textures ────────────────────────────────────────────────
2794    /// Draw a texture (GPU-side) at the specified rect.
2795    fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2796    /// Draw an image asset by name or path.
2797    fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2798    /// Load an image asset from memory.
2799    fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2800    /// Pre-warm the renderer with assets. Implementations can use this
2801    /// to populate texture atlases or warm up shader caches.
2802    fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2803
2804    /// Get the current pointer (mouse/touch) position.
2805    fn get_pointer_position(&self) -> [f32; 2] {
2806        [0.0, 0.0]
2807    }
2808
2809    // ── Data Visualization ───────────────────────────────────────────────
2810    /// Upload raw float data as a GPU texture for heatmap rendering.
2811    fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2812    /// Draw a heatmap using a previously uploaded data texture.
2813    fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2814
2815    // ── 3D Objects ───────────────────────────────────────────────────────
2816    /// Draw a 3D mesh.
2817    fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2818
2819    /// Draw a 3D mesh with full material and transform support.
2820    fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2821
2822    /// Set the 3D camera for perspective/orthographic projection.
2823    /// If not called, rendering defaults to the 2D orthographic projection.
2824    fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2825
2826    /// Push a 3D transform onto the transform stack.
2827    /// All subsequent drawing is affected until `pop_transform_3d`.
2828    fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2829
2830    /// Pop the most recently pushed 3D transform.
2831    fn pop_transform_3d(&mut self) {}
2832
2833    /// Render a 3D scene graph node. Reads position_3d, rotation_3d, scale_3d
2834    /// from the node and emits the appropriate draw call.
2835    /// Default implementation is a no-op; 3D renderers override this.
2836    ///
2837    /// `position`: [x, y, z] world-space position
2838    /// `rotation`: [x, y, z, w] quaternion rotation
2839    /// `scale`: [x, y, z] scale factors
2840    /// `color`: [r, g, b, a] base color for unlit rendering
2841    fn render_scene_node_3d(
2842        &mut self,
2843        _position: [f32; 3],
2844        _rotation: [f32; 4],
2845        _scale: [f32; 3],
2846        _color: [f32; 4],
2847        _meshes: &[Mesh],
2848    ) {
2849        // Default no-op: 2D renderers ignore 3D scene nodes
2850    }
2851
2852    /// Draw a linear gradient between two colors at the specified angle.
2853    fn draw_linear_gradient(
2854        &mut self,
2855        _rect: Rect,
2856        _start_color: [f32; 4],
2857        _end_color: [f32; 4],
2858        _angle: f32,
2859    ) {
2860    }
2861    /// Draw a radial gradient between two colors.
2862    fn draw_radial_gradient(
2863        &mut self,
2864        _rect: Rect,
2865        _inner_color: [f32; 4],
2866        _outer_color: [f32; 4],
2867    ) {
2868    }
2869    /// Draw a high-fidelity drop shadow for a rounded rectangle.
2870    fn draw_drop_shadow(
2871        &mut self,
2872        _rect: Rect,
2873        _radius: f32,
2874        _color: [f32; 4],
2875        _blur: f32,
2876        _spread: f32,
2877    ) {
2878    }
2879    /// Draw a dashed border for a rounded rectangle.
2880    fn stroke_dashed_rounded_rect(
2881        &mut self,
2882        _rect: Rect,
2883        _radius: f32,
2884        _color: [f32; 4],
2885        _width: f32,
2886        _dash: f32,
2887        _gap: f32,
2888    ) {
2889    }
2890    /// Draw a 9-slice / patch scaled image.
2891    fn draw_9slice(
2892        &mut self,
2893        _image_name: &str,
2894        _rect: Rect,
2895        _left: f32,
2896        _top: f32,
2897        _right: f32,
2898        _bottom: f32,
2899    ) {
2900    }
2901
2902    // ── Clipping ─────────────────────────────────────────────────────────
2903    /// Push a clip rectangle.  All subsequent drawing is clipped to `rect`.
2904    /// Implementations that do not support clipping may ignore this call.
2905    fn push_clip_rect(&mut self, _rect: Rect) {}
2906    /// Pop the most recently pushed clip rectangle.
2907    fn pop_clip_rect(&mut self) {}
2908    /// Get the current clip rectangle in screen coordinates.
2909    /// Returns a rect covering the entire screen if no clip is active.
2910    fn current_clip_rect(&self) -> Rect {
2911        Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2912    }
2913
2914    // ── Global opacity ───────────────────────────────────────────────────
2915    /// Set a global opacity multiplier applied to all subsequent draw calls
2916    /// until `pop_opacity` is called.  `opacity` is in [0.0, 1.0].
2917    fn push_opacity(&mut self, _opacity: f32) {}
2918    /// Restore the previous opacity level.
2919    fn pop_opacity(&mut self) {}
2920
2921    // ── Berserker Pipeline State ─────────────────────────────────────────
2922    fn set_theme(&mut self, _theme: ColorTheme) {}
2923    fn set_rage(&mut self, _rage: f32) {}
2924    fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2925    fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2926    /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
2927    fn set_scene(&mut self, _scene: &str) {}
2928
2929    // ── Export & Print ───────────────────────────────────────────────────
2930    /// Capture the current frame as a PNG byte buffer.
2931    fn capture_png(&mut self) -> Vec<u8> {
2932        Vec::new()
2933    }
2934    /// Trigger a native print dialog or spooling operation.
2935    fn print(&mut self) {}
2936
2937    fn set_scene_preset(&mut self, _preset: u32) {}
2938
2939    // ── Cyberpunk Effects ────────────────────────────────────────────────
2940    /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
2941    fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2942    /// Apply a Gungnir (Neon Glow) effect to the specified rect.
2943    fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2944    /// Apply a ManiGlow (Lunar Illuminator) effect.
2945    fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2946    /// Push a Mjolnir Slice (geometric clipping).
2947    fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2948    fn pop_mjolnir_slice(&mut self) {}
2949    /// Execute a render function with memoization.
2950    /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
2951    /// it may replay cached commands instead of executing the function.
2952    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2953    /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
2954    fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2955    fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2956    fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2957
2958    // ── Futuristic UI Compute & Volumetric ───────────────────────────────
2959    /// Dispatches a burst of GPU particles (e.g. fireworks, data streams).
2960    fn dispatch_particles(
2961        &mut self,
2962        _origin: [f32; 2],
2963        _count: u32,
2964        _effect_type: &str,
2965        _color: [f32; 4],
2966    ) {
2967    }
2968
2969    /// Draws a volumetric hologram into the specified bounding rectangle.
2970    fn draw_hologram(&mut self, _rect: Rect, _hologram_id: &str, _time: f32) {}
2971
2972    // ── Accessibility (ShieldWall) ───────────────────────────────────────
2973    fn set_aria_role(&mut self, _role: &str) {}
2974    fn set_aria_label(&mut self, _label: &str) {}
2975
2976    /// Register a shared element for Bifrost Bridge transitions.
2977    fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2978
2979    /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
2980    fn set_key(&mut self, _key: &str) {}
2981
2982    // ── Telemetry ────────────────────────────────────────────────────────
2983    /// Get real-time performance telemetry.
2984    fn get_telemetry(&self) -> TelemetryData {
2985        TelemetryData::default()
2986    }
2987
2988    // ── GPU State Management ─────────────────────────────────────────────
2989    /// Push a shadow state to the stack. All following draw calls will have this shadow.
2990    fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2991    /// Pop the last shadow state from the stack.
2992    fn pop_shadow(&mut self) {}
2993
2994    // ── VDOM & Scene Graph ───────────────────────────────────────────────
2995    /// Push a Virtual DOM node onto the stack for hierarchy tracking.
2996    fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2997    /// Pop the current Virtual DOM node from the stack.
2998    fn pop_vnode(&mut self) {}
2999    /// Register an event handler for the current VDOM node.
3000    fn register_handler(
3001        &mut self,
3002        _event_type: &str,
3003        _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
3004    ) {
3005    }
3006
3007    // ── Z-Index & Depth ──────────────────────────────────────────────────
3008    /// Set the current Z-index for depth sorting.
3009    /// Higher values appear closer to the viewer.
3010    fn set_z_index(&mut self, _z: f32) {}
3011    /// Get the current Z-index.
3012    fn get_z_index(&self) -> f32 {
3013        0.0
3014    }
3015
3016    // ── Vector Graphics ──────────────────────────────────────────────────
3017    /// Load an SVG model from raw bytes.
3018    fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
3019    /// Draw a pre-loaded SVG model.
3020    fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
3021    /// Draw a pre-loaded SVG model with a per-instance animation time offset.
3022    /// The offset shifts the animation phase, allowing multiple draws of the same
3023    /// SVG to animate independently. Default delegates to draw_svg (no offset).
3024    fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, _animation_time_offset: f32) {
3025        self.draw_svg(name, rect);
3026    }
3027    /// Serialize a pre-loaded SVG model back to SVG XML markup.
3028    /// Returns the serialized SVG string, or an error if the model is not loaded
3029    /// or serialization is not supported by this renderer.
3030    fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
3031        Err("SVG serialization not supported by this renderer".into())
3032    }
3033    /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
3034    /// The filter is evaluated and the result composited back into the SVG.
3035    /// Returns the filtered SVG as XML, or an error if not supported.
3036    fn apply_svg_filter(
3037        &mut self,
3038        _name: &str,
3039        _filter_id: &str,
3040        _region: Rect,
3041    ) -> Result<String, String> {
3042        Err("SVG filter not supported by this renderer".into())
3043    }
3044
3045    // ── GPU Transformations ──────────────────────────────────────────────
3046    /// Push a 2D transform (translation, scale, rotation) onto the stack.
3047    /// This transform should be applied to all subsequent draw calls until popped.
3048    /// Transform-only animations use this to avoid re-triggering the layout engine.
3049    fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
3050    /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
3051    /// [m11, m12, m21, m22, tx, ty].
3052    fn push_affine(&mut self, _transform: [f32; 6]) {}
3053    /// Pop the last 2D transform from the stack.
3054    fn pop_transform(&mut self) {}
3055    /// Return the resolved layout bounds for a specific node ID if it exists.
3056    fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
3057        None
3058    }
3059    /// Enable or disable the layout debug overlay (bounds, padding, margin).
3060    fn set_debug_layout(&mut self, _enabled: bool) {}
3061    /// Check if the layout debug overlay is currently enabled.
3062    fn get_debug_layout(&self) -> bool {
3063        false
3064    }
3065
3066    // ── Material Routing ─────────────────────────────────────────────────
3067    /// Set the active material for subsequent draw calls.
3068    /// Controls which pass a draw call is routed to in the multi-pass pipeline.
3069    fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3070    /// Return the currently active material (defaults to Opaque).
3071    fn current_material(&self) -> crate::material::DrawMaterial {
3072        crate::material::DrawMaterial::Opaque
3073    }
3074
3075    // ── Vili Interaction Paradigm ──────────────────────────────────────────
3076    /// Compute the user's velocity/intent vector.
3077    fn mimir_intent(&self) -> [f32; 2] {
3078        [0.0, 0.0]
3079    }
3080    /// Calculate magnetic coordinate warp towards an anchor.
3081    fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3082        if strength <= 0.0 {
3083            return pointer;
3084        }
3085        let cx = anchor_rect.x + anchor_rect.width / 2.0;
3086        let cy = anchor_rect.y + anchor_rect.height / 2.0;
3087        let dx = pointer[0] - cx;
3088        let dy = pointer[1] - cy;
3089        let dist = (dx * dx + dy * dy).sqrt();
3090        let radius = 120.0;
3091        if dist < radius && dist > 0.0 {
3092            let force = (1.0 - dist / radius) * strength;
3093            [pointer[0] - dx * force, pointer[1] - dy * force]
3094        } else {
3095            pointer
3096        }
3097    }
3098    /// Calculate kinematic glow intensity based on proximity.
3099    fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3100        let cx = bounds.x + bounds.width / 2.0;
3101        let cy = bounds.y + bounds.height / 2.0;
3102        let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3103        if dist < radius {
3104            (1.0 - dist / radius).clamp(0.0, 1.0)
3105        } else {
3106            0.0
3107        }
3108    }
3109    /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
3110    fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3111        let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3112        1.0 + (max_scale - 1.0) * prox
3113    }
3114    /// Sets the precise Vili SDF Shape boundary for hit-testing.
3115    fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3116
3117    // -- Portal / PhaseGate rendering -----------------------------------------
3118
3119    /// Begin rendering into the portal root layer instead of the inline tree.
3120    /// All draw calls between `enter_portal` and `exit_portal` are collected
3121    /// into a separate buffer that is composited AFTER the main tree.
3122    ///
3123    /// WHY separate buffer: The main tree may have clipping, transforms, or
3124    /// opacity that should NOT affect overlays. The portal layer renders on top
3125    /// of everything, ignoring the local coordinate system.
3126    fn enter_portal(&mut self, _z_index: i32) {}
3127
3128    /// Exit the portal layer and return to inline rendering.
3129    /// The portal content collected since `enter_portal` is now sealed --
3130    /// no more draw calls will be appended to it.
3131    fn exit_portal(&mut self) {}
3132
3133    /// Get the current viewport size in logical pixels.
3134    /// Used by portal content to size itself to the full screen.
3135    fn viewport_size(&self) -> Rect {
3136        Rect::new(0.0, 0.0, 1920.0, 1080.0)
3137    }
3138
3139    // -- Accessibility announcements -----------------------------------------
3140
3141    /// Announce a message to screen readers via the platform accessibility API.
3142    /// This call is non-blocking. The message is queued and the screen reader
3143    /// will speak it at its own pace.
3144    fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
3145}
3146
3147/// Utility for accessibility compliance (WCAG 2.1).
3148pub mod accessibility {
3149    /// Calculate the relative luminance of an sRGB color.
3150    pub fn relative_luminance(color: [f32; 4]) -> f32 {
3151        let f = |c: f32| {
3152            if c <= 0.03928 {
3153                c / 12.92
3154            } else {
3155                ((c + 0.055) / 1.055).powf(2.4)
3156            }
3157        };
3158        0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3159    }
3160
3161    /// Calculate the contrast ratio between two colors.
3162    pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3163        let l1 = relative_luminance(c1);
3164        let l2 = relative_luminance(c2);
3165        let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3166        (light + 0.05) / (dark + 0.05)
3167    }
3168}
3169/// Defines the hardware acceleration tier and feature set available to the renderer.
3170#[derive(
3171    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3172)]
3173pub enum RenderTier {
3174    /// High-performance GPU path (WebGPU / Vulkan / Metal / DX12) with full shader support.
3175    Tier1GPU = 0,
3176    /// Mid-tier GPU path (WebGL2 / OpenGL 3.3) with standard shader support.
3177    Tier2GPU = 1,
3178    /// Fallback software or basic hardware path (Canvas 2D / GDI+) with limited effects.
3179    Tier3Fallback = 2,
3180}
3181// =============================================================================
3182// BERSERKER UNIFORMS
3183// =============================================================================
3184use bytemuck::{Pod, Zeroable};
3185/// Fully themeable color palette for the Berserker pipeline.
3186#[repr(C)]
3187#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3188pub struct ColorTheme {
3189    pub primary_neon: [f32; 4], // (R, G, B, intensity)
3190    pub shatter_neon: [f32; 4],
3191    pub glass_base: [f32; 4],
3192    pub glass_edge: [f32; 4],
3193    pub rune_glow: [f32; 4],
3194    pub ember_core: [f32; 4],
3195    pub background_deep: [f32; 4],
3196    pub mani_glow: [f32; 4], // (R, G, B, radius)
3197    pub glass_blur_strength: f32,
3198    pub shatter_edge_width: f32,
3199    pub neon_bloom_radius: f32,
3200    pub rune_opacity: f32,
3201    /// Weight of adaptive tint from backdrop [0.0, 1.0].
3202    /// 0.0 = static theme tint, 1.0 = fully adaptive.
3203    pub glass_tint_adapt: f32,
3204    /// Per-frame glass IOR override. 0.0 = use shader default (1.45).
3205    pub glass_ior: f32,
3206    // Padding to match WGSL uniform buffer 16-byte alignment (total = 160 bytes)
3207    pub _pad0: f32,
3208    pub _pad1: f32,
3209}
3210impl ColorTheme {
3211    /// Asgard Mode: The high-fidelity "Cyberpunk Viking" aesthetic.
3212    pub fn asgard() -> Self {
3213        Self {
3214            primary_neon: [0.0, 1.0, 0.95, 1.2],
3215            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3216            glass_base: [0.04, 0.04, 0.06, 0.82],
3217            glass_edge: [0.0, 0.45, 0.55, 0.6],
3218            rune_glow: [0.75, 0.98, 1.0, 0.9],
3219            ember_core: [0.95, 0.12, 0.12, 1.0],
3220            background_deep: [0.01, 0.01, 0.03, 1.0],
3221            mani_glow: [0.7, 0.9, 1.0, 0.05],
3222            glass_blur_strength: 0.6,
3223            shatter_edge_width: 1.8,
3224            neon_bloom_radius: 0.022,
3225            rune_opacity: 0.55,
3226            glass_tint_adapt: 0.35,
3227            glass_ior: 1.45,
3228            _pad0: 0.0,
3229            _pad1: 0.0,
3230        }
3231    }
3232
3233    /// Midgard Mode: A clean, functional tactical HUD for standard operations.
3234    pub fn midgard() -> Self {
3235        Self {
3236            primary_neon: [0.2, 0.4, 0.6, 1.0], // Muted blue
3237            shatter_neon: [0.5, 0.5, 0.5, 1.0], // Neutral gray
3238            glass_base: [0.1, 0.12, 0.15, 1.0], // Solid slate
3239            glass_edge: [0.3, 0.35, 0.4, 1.0],  // Subtle border
3240            rune_glow: [0.8, 0.8, 0.8, 0.0],    // Runes disabled
3241            ember_core: [0.5, 0.5, 0.5, 1.0],
3242            background_deep: [0.05, 0.05, 0.07, 1.0],
3243            mani_glow: [0.0, 0.0, 0.0, 0.0], // No cursor glow
3244            glass_blur_strength: 0.0,        // No blur
3245            shatter_edge_width: 1.0,
3246            neon_bloom_radius: 0.0,
3247            rune_opacity: 0.0,
3248            glass_tint_adapt: 0.0,
3249            glass_ior: 1.0,
3250            _pad0: 0.0,
3251            _pad1: 0.0,
3252        }
3253    }
3254
3255    pub fn cyberpunk_viking() -> Self {
3256        Self::asgard()
3257    }
3258    pub fn vibrant_glass() -> Self {
3259        Self {
3260            primary_neon: [0.0, 1.0, 0.95, 1.2],
3261            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3262            glass_base: [0.55, 0.6, 0.7, 0.08], // Luminous cool tint
3263            glass_edge: [0.7, 0.85, 1.0, 0.45], // Subtle blue-white rim
3264            rune_glow: [0.75, 0.98, 1.0, 0.9],
3265            ember_core: [1.0, 0.4, 0.1, 1.0],
3266            background_deep: [0.05, 0.05, 0.1, 1.0],
3267            mani_glow: [0.7, 0.9, 1.0, 0.05],
3268            glass_blur_strength: 0.9,
3269            shatter_edge_width: 1.8,
3270            neon_bloom_radius: 0.022,
3271            rune_opacity: 0.55,
3272            glass_tint_adapt: 0.65,
3273            glass_ior: 1.45,
3274            _pad0: 0.0,
3275            _pad1: 0.0,
3276        }
3277    }
3278
3279    /// Berserker Mode: Blood-iron neon, aggressive contrast, forge-heated glass.
3280    pub fn berserker() -> Self {
3281        Self {
3282            primary_neon: [1.0, 0.08, 0.12, 1.8],
3283            shatter_neon: [0.95, 0.92, 0.88, 1.6],
3284            glass_base: [0.03, 0.02, 0.02, 0.88],
3285            glass_edge: [0.8, 0.35, 0.08, 0.7],
3286            rune_glow: [0.9, 0.72, 0.3, 1.0],
3287            ember_core: [0.98, 0.25, 0.05, 1.0],
3288            background_deep: [0.01, 0.005, 0.005, 1.0],
3289            mani_glow: [0.8, 0.2, 0.05, 0.08],
3290            glass_blur_strength: 0.85,
3291            shatter_edge_width: 2.8,
3292            neon_bloom_radius: 0.035,
3293            rune_opacity: 0.85,
3294            glass_tint_adapt: 0.15,
3295            glass_ior: 1.85,
3296            _pad0: 0.0,
3297            _pad1: 0.0,
3298        }
3299    }
3300}
3301impl Default for ColorTheme {
3302    fn default() -> Self {
3303        Self::vibrant_glass()
3304    }
3305}
3306/// Per-frame scene state for the Berserker pipeline.
3307#[repr(C)]
3308#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3309pub struct SceneUniforms {
3310    pub view: glam::Mat4,
3311    pub proj: glam::Mat4,
3312    pub time: f32,
3313    pub delta_time: f32,
3314    pub resolution: [f32; 2],
3315    pub mouse: [f32; 2],
3316    pub mouse_velocity: [f32; 2],
3317    pub shatter_origin: [f32; 2],
3318    pub shatter_time: f32,
3319    pub shatter_force: f32,
3320    pub berzerker_rage: f32,
3321    pub berzerker_mode: u32,
3322    pub scroll_offset: f32,
3323    pub scale_factor: f32,
3324    pub scene_type: u32,
3325    pub _pad: [f32; 3], // Align to 16 bytes if needed, but current struct is 4x16 + 4x16 + 4x16 + ...
3326}
3327
3328pub const SCENE_AURORA: u32 = 0;
3329pub const SCENE_VOID: u32 = 1;
3330pub const SCENE_NEBULA: u32 = 2;
3331pub const SCENE_GLITCH: u32 = 3;
3332pub const SCENE_YGGDRASIL: u32 = 4;
3333
3334impl SceneUniforms {
3335    pub fn new(width: f32, height: f32) -> Self {
3336        Self {
3337            view: glam::Mat4::IDENTITY,
3338            proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3339            time: 0.0,
3340            delta_time: 0.016,
3341            resolution: [width, height],
3342            mouse: [0.5, 0.5],
3343            mouse_velocity: [0.0, 0.0],
3344            shatter_origin: [0.5, 0.5],
3345            shatter_time: -100.0,
3346            shatter_force: 0.0,
3347            berzerker_rage: 0.0,
3348            berzerker_mode: 0,
3349            scroll_offset: 0.0,
3350            scale_factor: 1.0,
3351            scene_type: SCENE_AURORA,
3352            _pad: [0.0; 3],
3353        }
3354    }
3355}
3356/// A 3D mesh containing vertex and index data.
3357#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3358pub struct Mesh {
3359    pub vertices: Vec<[f32; 3]>,
3360    pub normals: Vec<[f32; 3]>,
3361    pub indices: Vec<u32>,
3362}
3363impl Mesh {
3364    pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3365        let mut cursor = std::io::Cursor::new(data);
3366        let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3367            Ok((Vec::new(), Default::default()))
3368        })?;
3369        let mut meshes = Vec::new();
3370        for m in models {
3371            let mesh = m.mesh;
3372            let vertices: Vec<[f32; 3]> = mesh
3373                .positions
3374                .chunks(3)
3375                .map(|c| [c[0], c[1], c[2]])
3376                .collect();
3377            let normals = if mesh.normals.is_empty() {
3378                vec![[0.0, 0.0, 1.0]; vertices.len()]
3379            } else {
3380                mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3381            };
3382            meshes.push(Mesh {
3383                vertices,
3384                normals,
3385                indices: mesh.indices,
3386            });
3387        }
3388        Ok(meshes)
3389    }
3390    pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3391        let mut cursor = std::io::Cursor::new(data);
3392        let stl = stl_io::read_stl(&mut cursor)?;
3393        let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3394        let mut indices = Vec::new();
3395        for face in stl.faces {
3396            indices.push(face.vertices[0] as u32);
3397            indices.push(face.vertices[1] as u32);
3398            indices.push(face.vertices[2] as u32);
3399        }
3400        let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3401        Ok(Mesh {
3402            vertices,
3403            normals,
3404            indices,
3405        })
3406    }
3407}
3408
3409// ══════════════════════════════════════════════════════════════════════════
3410// 3D TYPES — Phase 1: Camera, Transform, and 2.5D layer support
3411// ══════════════════════════════════════════════════════════════════════════
3412
3413/// A 3D transform: position, rotation (quaternion), and scale.
3414#[derive(Debug, Clone, Copy, PartialEq)]
3415pub struct Transform3D {
3416    pub position: glam::Vec3,
3417    pub rotation: glam::Quat,
3418    pub scale: glam::Vec3,
3419}
3420
3421impl Default for Transform3D {
3422    fn default() -> Self {
3423        Self {
3424            position: glam::Vec3::ZERO,
3425            rotation: glam::Quat::IDENTITY,
3426            scale: glam::Vec3::ONE,
3427        }
3428    }
3429}
3430
3431impl Transform3D {
3432    /// Convert this transform to a 4x4 model matrix.
3433    pub fn to_matrix(&self) -> glam::Mat4 {
3434        glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3435    }
3436
3437    /// Create a 2D-compatible transform (z=0, no rotation on z axis).
3438    pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3439        Self {
3440            position: glam::Vec3::new(x, y, 0.0),
3441            rotation: glam::Quat::from_rotation_z(rotation),
3442            scale: glam::Vec3::ONE,
3443        }
3444    }
3445}
3446
3447/// Camera definition for 3D rendering.
3448#[derive(Debug, Clone, Copy)]
3449pub struct Camera3D {
3450    /// World-space camera position.
3451    pub position: glam::Vec3,
3452    /// World-space point the camera looks at.
3453    pub target: glam::Vec3,
3454    /// World-space up vector.
3455    pub up: glam::Vec3,
3456    /// Field of view in radians (perspective) or half-height (orthographic).
3457    pub fov_y: f32,
3458    /// Near clipping plane distance.
3459    pub near: f32,
3460    /// Far clipping plane distance.
3461    pub far: f32,
3462    /// If true, use perspective projection. If false, use orthographic.
3463    pub perspective: bool,
3464    /// Aspect ratio (width / height). Used for perspective projection.
3465    pub aspect: f32,
3466}
3467
3468/// Material properties for 3D rendering.
3469#[derive(Debug, Clone, Copy, PartialEq)]
3470pub struct Material3D {
3471    /// Base color (RGBA).
3472    pub base_color: [f32; 4],
3473    /// Metallic factor (0 = dielectric, 1 = metallic).
3474    pub metallic: f32,
3475    /// Roughness factor (0 = mirror, 1 = fully diffuse).
3476    pub roughness: f32,
3477    /// Emissive color (RGB) for self-illumination.
3478    pub emissive: [f32; 3],
3479    /// Opacity (0 = transparent, 1 = opaque).
3480    pub opacity: f32,
3481}
3482
3483impl Default for Material3D {
3484    fn default() -> Self {
3485        Self {
3486            base_color: [1.0, 1.0, 1.0, 1.0],
3487            metallic: 0.0,
3488            roughness: 0.5,
3489            emissive: [0.0, 0.0, 0.0],
3490            opacity: 1.0,
3491        }
3492    }
3493}
3494
3495impl Material3D {
3496    /// Create a simple unlit material with just a color.
3497    pub fn unlit(color: [f32; 4]) -> Self {
3498        Self {
3499            base_color: color,
3500            metallic: 0.0,
3501            roughness: 1.0,
3502            emissive: [0.0, 0.0, 0.0],
3503            opacity: color[3],
3504        }
3505    }
3506
3507    /// Create a metallic material.
3508    pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3509        Self {
3510            base_color: color,
3511            metallic: 1.0,
3512            roughness: roughness.clamp(0.0, 1.0),
3513            emissive: [0.0, 0.0, 0.0],
3514            opacity: color[3],
3515        }
3516    }
3517}
3518
3519impl Default for Camera3D {
3520    fn default() -> Self {
3521        Self {
3522            position: glam::Vec3::new(0.0, 0.0, 10.0),
3523            target: glam::Vec3::ZERO,
3524            up: glam::Vec3::Y,
3525            fov_y: 45.0f32.to_radians(),
3526            near: 0.1,
3527            far: 1000.0,
3528            perspective: true,
3529            aspect: 16.0 / 9.0,
3530        }
3531    }
3532}
3533
3534impl Camera3D {
3535    /// Compute the view matrix (world → camera space).
3536    pub fn view_matrix(&self) -> glam::Mat4 {
3537        glam::Mat4::look_at_lh(self.position, self.target, self.up)
3538    }
3539
3540    /// Compute the projection matrix.
3541    pub fn projection_matrix(&self) -> glam::Mat4 {
3542        if self.perspective {
3543            glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3544        } else {
3545            // Orthographic with fov_y as half-height
3546            let top = self.fov_y;
3547            let right = top * self.aspect;
3548            glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3549        }
3550    }
3551
3552    /// Compute the combined view-projection matrix.
3553    pub fn view_projection(&self) -> glam::Mat4 {
3554        self.projection_matrix() * self.view_matrix()
3555    }
3556}
3557
3558/// FrameRenderer extends Renderer with frame lifecycle management.
3559/// It is typically implemented by the host windowing/rendering environment.
3560pub trait FrameRenderer<E = ()>: Renderer {
3561    fn begin_frame(&mut self) -> E;
3562    fn render_frame(&mut self) {
3563        // Default implementation does nothing - override for custom frame rendering
3564    }
3565    fn end_frame(&mut self, encoder: E);
3566}
3567use std::sync::Arc;
3568type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3569/// State wrapper that owns a value and notifies subscribers when changed
3570#[derive(Clone)]
3571pub struct State<T: Clone + Send + Sync + 'static> {
3572    swap: Arc<arc_swap::ArcSwap<T>>,
3573    metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3574    #[cfg(not(target_arch = "wasm32"))]
3575    tvar: Arc<stm::TVar<T>>,
3576    #[cfg(not(target_arch = "wasm32"))]
3577    metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3578    subscribers: SubscriberList<T>,
3579    version: Arc<std::sync::atomic::AtomicU64>,
3580    resolution: agents::ConflictResolution,
3581}
3582impl<T: Clone + Send + Sync + 'static> State<T> {
3583    /// Create a new State with initial value
3584    pub fn new(value: T) -> Self {
3585        #[cfg(not(target_arch = "wasm32"))]
3586        let tvar = Arc::new(stm::TVar::new(value.clone()));
3587        #[cfg(not(target_arch = "wasm32"))]
3588        let metadata_tvar = Arc::new(stm::TVar::new(None));
3589        Self {
3590            swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3591            metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3592            #[cfg(not(target_arch = "wasm32"))]
3593            tvar,
3594            #[cfg(not(target_arch = "wasm32"))]
3595            metadata_tvar,
3596            subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3597            version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3598            resolution: agents::ConflictResolution::default(),
3599        }
3600    }
3601    /// Set the conflict resolution strategy for this state.
3602    pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3603        self.resolution = resolution;
3604        self
3605    }
3606    /// Get the current value
3607    pub fn get(&self) -> T {
3608        (**self.swap.load()).clone()
3609    }
3610    /// Set a new value, notifying all subscribers. Applies conflict resolution if agents are present.
3611    pub fn set(&self, value: T) {
3612        #[cfg(not(target_arch = "wasm32"))]
3613        let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3614            let new_meta = agents::get_current_mutation_metadata();
3615            let existing_meta = self.metadata_tvar.read(tx)?;
3616            let mut skip = false;
3617            if self.resolution == agents::ConflictResolution::PriorityWins
3618                && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3619                && new_m.priority < old_m.priority
3620            {
3621                skip = true;
3622            }
3623            if !skip {
3624                self.tvar.write(tx, value.clone())?;
3625                self.metadata_tvar.write(tx, new_meta)?;
3626                Ok((false, value.clone(), new_meta))
3627            } else {
3628                Ok((true, self.tvar.read(tx)?, existing_meta))
3629            }
3630        });
3631        #[cfg(target_arch = "wasm32")]
3632        let (was_skipped, final_val, final_meta) =
3633            (false, value, agents::get_current_mutation_metadata());
3634        if was_skipped {
3635            if let (Some(new_m), Some(old_m)) =
3636                (agents::get_current_mutation_metadata(), final_meta)
3637            {
3638                agents::notify_conflict(agents::ConflictEvent {
3639                    agent_id: new_m.agent_id,
3640                    priority: new_m.priority,
3641                    existing_agent_id: old_m.agent_id,
3642                    existing_priority: old_m.priority,
3643                    timestamp_ms: new_m.timestamp_ms,
3644                });
3645            }
3646            return;
3647        }
3648        self.swap.store(Arc::new(final_val.clone()));
3649        self.metadata_swap.store(Arc::new(final_meta));
3650        self.version
3651            .fetch_add(1, std::sync::atomic::Ordering::Release);
3652        let subs = Arc::clone(&self.subscribers);
3653        if crate::is_batching() {
3654            crate::enqueue_batch_task(Box::new(move || {
3655                let s = subs.lock().unwrap();
3656                for cb in s.iter() {
3657                    cb(&final_val);
3658                }
3659            }));
3660        } else {
3661            let s = subs.lock().unwrap();
3662            for cb in s.iter() {
3663                cb(&final_val);
3664            }
3665        }
3666    }
3667    pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3668        #[cfg(not(target_arch = "wasm32"))]
3669        {
3670            let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3671                let new_meta = agents::get_current_mutation_metadata();
3672                let existing_meta = self.metadata_tvar.read(tx)?;
3673                let mut skip = false;
3674                if self.resolution == agents::ConflictResolution::PriorityWins
3675                    && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3676                    && new_m.priority < old_m.priority
3677                {
3678                    skip = true;
3679                }
3680                if !skip {
3681                    let current = self.tvar.read(tx)?;
3682                    let next = f(&current);
3683                    self.tvar.write(tx, next.clone())?;
3684                    self.metadata_tvar.write(tx, new_meta)?;
3685                    Ok((false, next, new_meta))
3686                } else {
3687                    Ok((true, self.tvar.read(tx)?, existing_meta))
3688                }
3689            });
3690            if was_skipped {
3691                if let (Some(new_m), Some(old_m)) =
3692                    (agents::get_current_mutation_metadata(), final_meta)
3693                {
3694                    agents::notify_conflict(agents::ConflictEvent {
3695                        agent_id: new_m.agent_id,
3696                        priority: new_m.priority,
3697                        existing_agent_id: old_m.agent_id,
3698                        existing_priority: old_m.priority,
3699                        timestamp_ms: new_m.timestamp_ms,
3700                    });
3701                }
3702                return;
3703            }
3704            self.swap.store(Arc::new(final_val.clone()));
3705            self.metadata_swap.store(Arc::new(final_meta));
3706            self.version
3707                .fetch_add(1, std::sync::atomic::Ordering::Release);
3708            let subs = Arc::clone(&self.subscribers);
3709            if crate::is_batching() {
3710                crate::enqueue_batch_task(Box::new(move || {
3711                    let s = subs.lock().unwrap();
3712                    for cb in s.iter() {
3713                        cb(&final_val);
3714                    }
3715                }));
3716            } else {
3717                let s = subs.lock().unwrap();
3718                for cb in s.iter() {
3719                    cb(&final_val);
3720                }
3721            }
3722        }
3723        #[cfg(target_arch = "wasm32")]
3724        {
3725            self.set(f(&self.get()));
3726        }
3727    }
3728    /// Get current version
3729    pub fn version(&self) -> u64 {
3730        self.version.load(std::sync::atomic::Ordering::Acquire)
3731    }
3732    /// Subscribe to state changes
3733    pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3734        self.subscribers.lock().unwrap().push(Box::new(callback));
3735    }
3736}
3737use crate::runtime::NodeStateSnapshot;
3738use std::sync::OnceLock;
3739use std::sync::atomic::{AtomicBool, Ordering};
3740/// Global application state registry.
3741pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3742#[cfg(not(target_arch = "wasm32"))]
3743static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3744static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3745pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3746pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3747type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3748static BATCH_QUEUE: BatchQueue = OnceLock::new();
3749/// Global write lock to serialize updates to SYSTEM_STATE and KNOWLEDGE_TVAR,
3750/// preventing parallel race conditions between STM transactions and the lock-free reader state.
3751static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3752/// Returns true if state updates are currently being batched.
3753pub fn is_batching() -> bool {
3754    IS_BATCHING.load(Ordering::Acquire)
3755}
3756/// Returns true if the system is currently in the render phase.
3757pub fn is_rendering() -> bool {
3758    IS_RENDERING.load(Ordering::Acquire)
3759}
3760/// Signals the start of the render phase. Mutations during this phase trigger warnings.
3761pub fn begin_render_phase() {
3762    IS_RENDERING.store(true, Ordering::Release);
3763}
3764/// Signals the end of the render phase.
3765pub fn end_render_phase() {
3766    IS_RENDERING.store(false, Ordering::Release);
3767}
3768/// Enqueues a notification task to be run when the current batch flushes.
3769pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3770    let mut queue = BATCH_QUEUE
3771        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3772        .lock()
3773        .unwrap();
3774    queue.push(task);
3775}
3776/// Executes multiple state updates in a single batch, deferring all subscriber
3777/// notifications until the closure completes. This prevents layout thrashing
3778/// and redundant render cycles when modifying multiple independent states.
3779pub fn batch<F: FnOnce()>(f: F) {
3780    if IS_BATCHING.swap(true, Ordering::AcqRel) {
3781        // Already inside a batch, just execute
3782        f();
3783        return;
3784    }
3785    f();
3786    IS_BATCHING.store(false, Ordering::Release);
3787    let mut queue = BATCH_QUEUE
3788        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3789        .lock()
3790        .unwrap();
3791    let tasks: Vec<_> = queue.drain(..).collect();
3792    drop(queue);
3793    for task in tasks {
3794        task();
3795    }
3796}
3797/// Get a reference to the global system state.
3798pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3799    SYSTEM_STATE
3800        .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3801        .clone()
3802}
3803pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3804    get_system_state().load()
3805}
3806pub fn update_system_state<F>(f: F)
3807where
3808    F: FnOnce(&KnowledgeState) -> KnowledgeState,
3809{
3810    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3811    if is_rendering() {
3812        log::warn!(
3813            "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3814        );
3815    }
3816    LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3817    let swap = get_system_state();
3818    let current = swap.load();
3819    let new_state = Arc::new(f(&current));
3820    swap.store(Arc::clone(&new_state));
3821    #[cfg(not(target_arch = "wasm32"))]
3822    {
3823        let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3824        stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3825    }
3826}
3827pub fn transact_system_state<F>(f: F)
3828where
3829    F: Fn(&KnowledgeState) -> KnowledgeState,
3830{
3831    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3832    #[cfg(not(target_arch = "wasm32"))]
3833    {
3834        if is_rendering() {
3835            log::warn!(
3836                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3837            );
3838        }
3839        let tvar = KNOWLEDGE_TVAR
3840            .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3841            .clone();
3842        let new_state = stm::atomically(move |tx| {
3843            let current = tvar.read(tx)?;
3844            let next = f(&current);
3845            tvar.write(tx, next.clone())?;
3846            Ok(next)
3847        });
3848        get_system_state().store(Arc::new(new_state));
3849    }
3850    #[cfg(target_arch = "wasm32")]
3851    {
3852        if is_rendering() {
3853            log::warn!(
3854                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3855            );
3856        }
3857        update_system_state(f);
3858    }
3859}
3860impl KnowledgeState {
3861    /// Create a new empty KnowledgeState.
3862    pub fn new() -> Self {
3863        Self::default()
3864    }
3865    /// Set a component's internal state.
3866    pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3867        self.component_states
3868            .insert(id, Arc::new(std::sync::RwLock::new(state)));
3869    }
3870    /// Get a reference to a component's internal state.
3871    pub fn get_component_state<T: 'static + Send + Sync>(
3872        &self,
3873        id: u64,
3874    ) -> Option<Arc<std::sync::RwLock<T>>> {
3875        let lock = self.component_states.get(&id)?;
3876        // Attempt to clone the Arc and downcast the inner RwLock<dyn Any> to RwLock<T>
3877        // We use a two-step approach: check if the inner type matches via Any, then transmute the Arc
3878        // SAFETY: We verify the type via Any::is::<T> before transmuting
3879        let any_ref = lock.read().ok()?;
3880        if any_ref.is::<T>() {
3881            // Type matches — safe to transmute the Arc
3882            drop(any_ref);
3883            let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3884            // Transmute Arc<RwLock<dyn Any>> to Arc<RwLock<T>>
3885            // This is safe because we just verified the inner type is T
3886            Some(unsafe {
3887                let raw = Arc::into_raw(cloned);
3888                Arc::from_raw(raw as *const std::sync::RwLock<T>)
3889            })
3890        } else {
3891            None
3892        }
3893    }
3894    /// Add a new fragment to memory.
3895    pub fn remember(&mut self, fragment: KnowledgeFragment) {
3896        self.fragments.insert(fragment.id.clone(), fragment);
3897    }
3898    /// Process a search query against the local knowledge base.
3899    pub fn process_query(&mut self, query: &str) {
3900        let query_lower = query.to_lowercase();
3901        let mut results: Vec<(f32, String)> = self
3902            .fragments
3903            .iter()
3904            .map(|(id, frag)| {
3905                let mut score = 0.0;
3906                if frag.summary.to_lowercase().contains(&query_lower) {
3907                    score += 1.0;
3908                }
3909                if frag.source.to_lowercase().contains(&query_lower) {
3910                    score += 0.5;
3911                }
3912                (score, id.clone())
3913            })
3914            .filter(|(score, _)| *score > 0.0)
3915            .collect();
3916        // Sort by relevance score
3917        results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3918        self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3919    }
3920    /// Captures a snapshot of the current state for debugging and hot-reloading.
3921    pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3922        let mut snapshots = Vec::new();
3923        // Snapshots of agentic fragments
3924        for frag in self.fragments.values() {
3925            if let Ok(val) = serde_json::to_value(frag) {
3926                snapshots.push(NodeStateSnapshot { id: 0, state: val });
3927            }
3928        }
3929        snapshots
3930    }
3931}
3932/// A read/write projection into a `State<T>` owned elsewhere.
3933#[derive(Clone)]
3934pub struct Binding<T: Clone + Send + Sync + 'static> {
3935    swap: Arc<arc_swap::ArcSwap<T>>,
3936    #[cfg(not(target_arch = "wasm32"))]
3937    tvar: Arc<stm::TVar<T>>,
3938    version: Arc<std::sync::atomic::AtomicU64>,
3939}
3940impl<T: Clone + Send + Sync + 'static> Binding<T> {
3941    /// Create a binding from a State
3942    pub fn from_state(state: &State<T>) -> Self {
3943        Self {
3944            swap: Arc::clone(&state.swap),
3945            #[cfg(not(target_arch = "wasm32"))]
3946            tvar: Arc::clone(&state.tvar),
3947            version: Arc::clone(&state.version),
3948        }
3949    }
3950    /// Get the current value
3951    pub fn get(&self) -> T {
3952        (**self.swap.load()).clone()
3953    }
3954    /// Set a new value
3955    pub fn set(&self, value: T) {
3956        self.swap.store(Arc::new(value.clone()));
3957        #[cfg(not(target_arch = "wasm32"))]
3958        {
3959            let tvar = Arc::clone(&self.tvar);
3960            let v = value.clone();
3961            stm::atomically(move |tx| tvar.write(tx, v.clone()));
3962        }
3963        self.version
3964            .fetch_add(1, std::sync::atomic::Ordering::Release);
3965    }
3966    /// Get current version
3967    pub fn version(&self) -> u64 {
3968        self.version.load(std::sync::atomic::Ordering::Acquire)
3969    }
3970}
3971#[cfg(not(target_arch = "wasm32"))]
3972pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3973where
3974    A: Clone + Send + Sync + 'static,
3975    B: Clone + Send + Sync + 'static,
3976    F: Fn(&A, &B) -> (A, B),
3977{
3978    let tvar_a = Arc::clone(&state_a.tvar);
3979    let tvar_b = Arc::clone(&state_b.tvar);
3980    let (new_a, new_b) = stm::atomically(move |tx| {
3981        let a = tvar_a.read(tx)?;
3982        let b = tvar_b.read(tx)?;
3983        let (na, nb) = f(&a, &b);
3984        tvar_a.write(tx, na.clone())?;
3985        tvar_b.write(tx, nb.clone())?;
3986        Ok((na, nb))
3987    });
3988    state_a.swap.store(Arc::new(new_a.clone()));
3989    state_b.swap.store(Arc::new(new_b.clone()));
3990    state_a
3991        .version
3992        .fetch_add(1, std::sync::atomic::Ordering::Release);
3993    state_b
3994        .version
3995        .fetch_add(1, std::sync::atomic::Ordering::Release);
3996    let subs_a = Arc::clone(&state_a.subscribers);
3997    let subs_b = Arc::clone(&state_b.subscribers);
3998    if crate::is_batching() {
3999        crate::enqueue_batch_task(Box::new(move || {
4000            {
4001                let s = subs_a.lock().unwrap();
4002                for cb in s.iter() {
4003                    cb(&new_a);
4004                }
4005            }
4006            {
4007                let s = subs_b.lock().unwrap();
4008                for cb in s.iter() {
4009                    cb(&new_b);
4010                }
4011            }
4012        }));
4013    } else {
4014        {
4015            let s = subs_a.lock().unwrap();
4016            for cb in s.iter() {
4017                cb(&new_a);
4018            }
4019        }
4020        {
4021            let s = subs_b.lock().unwrap();
4022            for cb in s.iter() {
4023                cb(&new_b);
4024            }
4025        }
4026    }
4027}
4028use std::any::TypeId;
4029use std::sync::Mutex;
4030/// Global environment storage using TypeId as keys.
4031pub(crate) static ENVIRONMENT: OnceLock<
4032    Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
4033> = OnceLock::new();
4034/// Environment key type for accessing ambient values
4035/// Implement this trait to define a new environment key.
4036pub trait EnvKey: 'static + Send + Sync {
4037    /// The type of value stored in the environment
4038    type Value: Clone + Send + Sync + 'static;
4039    /// Get a default value for this key
4040    fn default_value() -> Self::Value;
4041}
4042/// Key for accessing the Yggdrasil design token tree
4043pub struct YggdrasilKey;
4044impl EnvKey for YggdrasilKey {
4045    type Value = YggdrasilTokens;
4046    fn default_value() -> Self::Value {
4047        default_tokens()
4048    }
4049}
4050// Duplicate AssetKey removed - original definition at line 63
4051/// System appearance (Light/Dark mode)
4052#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4053pub enum Appearance {
4054    Light,
4055    Dark,
4056}
4057/// Orientation for layouts
4058#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4059pub enum Orientation {
4060    Horizontal,
4061    Vertical,
4062}
4063/// Placement configuration for placing a view within a Grid layout.
4064#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4065pub struct GridPlacement {
4066    /// 0-based column index. Negative values count from the end of columns.
4067    pub column: i32,
4068    /// Number of columns the view spans (default is 1).
4069    pub column_span: u32,
4070    /// 0-based row index. Negative values count from the end of rows.
4071    pub row: i32,
4072    /// Number of rows the view spans (default is 1).
4073    pub row_span: u32,
4074}
4075/// Cross-axis alignment for layout containers.
4076#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4077pub enum Alignment {
4078    #[default]
4079    Center,
4080    Leading,
4081    Trailing,
4082    Top,
4083    Bottom,
4084}
4085/// Main-axis distribution for linear layout containers.
4086#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4087pub enum Distribution {
4088    #[default]
4089    Fill,
4090    Center,
4091    Leading,
4092    Trailing,
4093    SpaceBetween,
4094    SpaceAround,
4095    SpaceEvenly,
4096}
4097/// A color represented by RGBA components in the [0.0, 1.0] range.
4098#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4099pub struct Color {
4100    pub r: f32,
4101    pub g: f32,
4102    pub b: f32,
4103    pub a: f32,
4104}
4105impl Color {
4106    pub const BLACK: Color = Color {
4107        r: 0.0,
4108        g: 0.0,
4109        b: 0.0,
4110        a: 1.0,
4111    };
4112    pub const WHITE: Color = Color {
4113        r: 1.0,
4114        g: 1.0,
4115        b: 1.0,
4116        a: 1.0,
4117    };
4118    pub const TRANSPARENT: Color = Color {
4119        r: 0.0,
4120        g: 0.0,
4121        b: 0.0,
4122        a: 0.0,
4123    };
4124    pub const RED: Color = Color {
4125        r: 1.0,
4126        g: 0.0,
4127        b: 0.0,
4128        a: 1.0,
4129    };
4130    pub const GREEN: Color = Color {
4131        r: 0.0,
4132        g: 1.0,
4133        b: 0.0,
4134        a: 1.0,
4135    };
4136    pub const BLUE: Color = Color {
4137        r: 0.0,
4138        g: 0.0,
4139        b: 1.0,
4140        a: 1.0,
4141    };
4142    pub const VIKING_GOLD: Color = Color {
4143        r: 1.0,
4144        g: 0.84,
4145        b: 0.0,
4146        a: 1.0,
4147    };
4148    pub const MAGENTA_LIQUID: Color = Color {
4149        r: 1.0,
4150        g: 0.0,
4151        b: 1.0,
4152        a: 1.0,
4153    };
4154    pub const TACTICAL_OBSIDIAN: Color = Color {
4155        r: 0.05,
4156        g: 0.05,
4157        b: 0.07,
4158        a: 1.0,
4159    };
4160    /// Calculate the relative luminance of the color as defined by WCAG 2.x
4161    pub fn relative_luminance(&self) -> f32 {
4162        fn res(c: f32) -> f32 {
4163            if c <= 0.03928 {
4164                c / 12.92
4165            } else {
4166                ((c + 0.055) / 1.055).powf(2.4)
4167            }
4168        }
4169        0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4170    }
4171    /// Calculate the contrast ratio between this color and another color
4172    pub fn contrast_ratio(&self, other: &Color) -> f32 {
4173        let l1 = self.relative_luminance();
4174        let l2 = other.relative_luminance();
4175        if l1 > l2 {
4176            (l1 + 0.05) / (l2 + 0.05)
4177        } else {
4178            (l2 + 0.05) / (l1 + 0.05)
4179        }
4180    }
4181    pub const CYAN: Color = Color {
4182        r: 0.0,
4183        g: 1.0,
4184        b: 1.0,
4185        a: 1.0,
4186    };
4187    pub const YELLOW: Color = Color {
4188        r: 1.0,
4189        g: 1.0,
4190        b: 0.0,
4191        a: 1.0,
4192    };
4193    pub const MAGENTA: Color = Color {
4194        r: 1.0,
4195        g: 0.0,
4196        b: 1.0,
4197        a: 1.0,
4198    };
4199    pub const GRAY: Color = Color {
4200        r: 0.5,
4201        g: 0.5,
4202        b: 0.5,
4203        a: 1.0,
4204    };
4205    /// Create a new color from RGBA components.
4206    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4207        Self { r, g, b, a }
4208    }
4209    /// Convert the color to a [r, g, b, a] array.
4210    pub fn as_array(&self) -> [f32; 4] {
4211        [self.r, self.g, self.b, self.a]
4212    }
4213
4214    /// Return a new color with lightness increased by `amount`.
4215    ///
4216    /// Adds `amount` to each RGB channel and clamps to [0.0, 1.0].
4217    /// This is a simple sRGB lightness adjustment, not perceptually uniform.
4218    /// For perceptually uniform adjustments, use OKLCH via cvkg-themes.
4219    pub fn lighten(&self, amount: f32) -> Self {
4220        Self {
4221            r: (self.r + amount).clamp(0.0, 1.0),
4222            g: (self.g + amount).clamp(0.0, 1.0),
4223            b: (self.b + amount).clamp(0.0, 1.0),
4224            a: self.a,
4225        }
4226    }
4227
4228    /// Return a new color with lightness decreased by `amount`.
4229    pub fn darken(&self, amount: f32) -> Self {
4230        Self {
4231            r: (self.r - amount).clamp(0.0, 1.0),
4232            g: (self.g - amount).clamp(0.0, 1.0),
4233            b: (self.b - amount).clamp(0.0, 1.0),
4234            a: self.a,
4235        }
4236    }
4237}
4238impl View for Color {
4239    type Body = Never;
4240    fn body(self) -> Self::Body {
4241        unreachable!()
4242    }
4243    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4244        renderer.fill_rect(rect, self.as_array());
4245    }
4246}
4247/// Key for accessing the current system appearance
4248pub struct AppearanceKey;
4249impl EnvKey for AppearanceKey {
4250    type Value = Appearance;
4251    fn default_value() -> Self::Value {
4252        Appearance::Dark // Default to Dark (Ginnungagap) for Berserker aesthetic
4253    }
4254}
4255/// StyleResolver provides high-level access to themed values from the environment.
4256pub struct StyleResolver;
4257impl StyleResolver {
4258    /// Resolve a color from the current environment
4259    pub fn color(key: &str) -> String {
4260        let tokens = Environment::<YggdrasilKey>::new().get();
4261        let appearance = Environment::<AppearanceKey>::new().get();
4262        let is_dark = appearance == Appearance::Dark;
4263        tokens
4264            .get_color(key, is_dark)
4265            .unwrap_or_else(|| "#FF00FF".to_string()) // Default to MuspelMagenta on failure
4266    }
4267    /// Resolve a generic token value
4268    pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4269        let tokens = Environment::<YggdrasilKey>::new().get();
4270        let appearance = Environment::<AppearanceKey>::new().get();
4271        let is_dark = appearance == Appearance::Dark;
4272        tokens.get(category, key, is_dark)
4273    }
4274    /// Resolve a color from the current environment as a [f32; 4] RGBA array.
4275    /// Returns the color value for the current appearance (light/dark).
4276    /// Falls back to magenta (#FF00FF) if the key is not found.
4277    pub fn color_array(key: &str) -> [f32; 4] {
4278        let hex = Self::color(key);
4279        parse_hex_color(&hex)
4280    }
4281}
4282
4283/// Parse a hex color string (#RRGGBB or #RRGGBBAA) into [f32; 4] RGBA.
4284fn parse_hex_color(hex: &str) -> [f32; 4] {
4285    let hex = hex.trim_start_matches('#');
4286    if hex.len() >= 6 {
4287        let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4288        let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4289        let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4290        let a = if hex.len() >= 8 {
4291            u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4292        } else {
4293            1.0
4294        };
4295        [r, g, b, a]
4296    } else {
4297        [1.0, 0.0, 1.0, 1.0] // Magenta fallback
4298    }
4299}
4300
4301/// The authoritative Cyberpunk Viking default tokens
4302pub fn default_tokens() -> YggdrasilTokens {
4303    let mut tokens = YggdrasilTokens::new();
4304    // Core Norse Colorways
4305    tokens.color.insert(
4306        "background".to_string(),
4307        TokenValue::Single {
4308            value: "#000000".to_string(), // Ginnungagap (The Void)
4309        },
4310    );
4311    tokens.color.insert(
4312        "primary".to_string(),
4313        TokenValue::Single {
4314            value: "#00FFFF".to_string(), // NiflCyan (Aesir Primary)
4315        },
4316    );
4317    tokens.color.insert(
4318        "secondary".to_string(),
4319        TokenValue::Single {
4320            value: "#FF00FF".to_string(), // MuspelMagenta (Berserker Secondary)
4321        },
4322    );
4323    tokens.color.insert(
4324        "surface".to_string(),
4325        TokenValue::Adaptive {
4326            light: "#FFFFFF".to_string(),
4327            dark: "#121212".to_string(),
4328        },
4329    );
4330    tokens.color.insert(
4331        "text".to_string(),
4332        TokenValue::Adaptive {
4333            light: "#000000".to_string(),
4334            dark: "#FFFFFF".to_string(),
4335        },
4336    );
4337    // Semantic component tokens
4338    tokens.color.insert(
4339        "surface_elevated".to_string(),
4340        TokenValue::Adaptive {
4341            light: "#FFFFFF".to_string(),
4342            dark: "#1A1A24".to_string(),
4343        },
4344    );
4345    tokens.color.insert(
4346        "surface_overlay".to_string(),
4347        TokenValue::Adaptive {
4348            light: "#FFFFFF".to_string(),
4349            dark: "#1E1E2E".to_string(),
4350        },
4351    );
4352    tokens.color.insert(
4353        "border".to_string(),
4354        TokenValue::Adaptive {
4355            light: "#D0D0D8".to_string(),
4356            dark: "#2A2A3A".to_string(),
4357        },
4358    );
4359    tokens.color.insert(
4360        "border_strong".to_string(),
4361        TokenValue::Adaptive {
4362            light: "#A0A0B0".to_string(),
4363            dark: "#3A3A50".to_string(),
4364        },
4365    );
4366    tokens.color.insert(
4367        "text_muted".to_string(),
4368        TokenValue::Adaptive {
4369            light: "#606070".to_string(),
4370            dark: "#8080A0".to_string(),
4371        },
4372    );
4373    tokens.color.insert(
4374        "text_dim".to_string(),
4375        TokenValue::Adaptive {
4376            light: "#9090A0".to_string(),
4377            dark: "#505070".to_string(),
4378        },
4379    );
4380    tokens.color.insert(
4381        "accent".to_string(),
4382        TokenValue::Single {
4383            value: "#00FFFF".to_string(), // NiflCyan
4384        },
4385    );
4386    tokens.color.insert(
4387        "accent_hover".to_string(),
4388        TokenValue::Single {
4389            value: "#33FFFF".to_string(),
4390        },
4391    );
4392    tokens.color.insert(
4393        "success".to_string(),
4394        TokenValue::Single {
4395            value: "#00E676".to_string(),
4396        },
4397    );
4398    tokens.color.insert(
4399        "warning".to_string(),
4400        TokenValue::Single {
4401            value: "#FFB300".to_string(),
4402        },
4403    );
4404    tokens.color.insert(
4405        "error".to_string(),
4406        TokenValue::Single {
4407            value: "#FF5252".to_string(),
4408        },
4409    );
4410    tokens.color.insert(
4411        "info".to_string(),
4412        TokenValue::Single {
4413            value: "#448AFF".to_string(),
4414        },
4415    );
4416    tokens.color.insert(
4417        "hover".to_string(),
4418        TokenValue::Adaptive {
4419            light: "#F0F0F5".to_string(),
4420            dark: "#252535".to_string(),
4421        },
4422    );
4423    tokens.color.insert(
4424        "active".to_string(),
4425        TokenValue::Adaptive {
4426            light: "#E0E0EB".to_string(),
4427            dark: "#303045".to_string(),
4428        },
4429    );
4430    tokens.color.insert(
4431        "disabled".to_string(),
4432        TokenValue::Adaptive {
4433            light: "#E8E8F0".to_string(),
4434            dark: "#1A1A28".to_string(),
4435        },
4436    );
4437    tokens.color.insert(
4438        "disabled_text".to_string(),
4439        TokenValue::Adaptive {
4440            light: "#B0B0C0".to_string(),
4441            dark: "#404060".to_string(),
4442        },
4443    );
4444    tokens.color.insert(
4445        "focus_ring".to_string(),
4446        TokenValue::Single {
4447            value: "#00FFFF".to_string(),
4448        },
4449    );
4450    tokens.color.insert(
4451        "shadow".to_string(),
4452        TokenValue::Adaptive {
4453            light: "#00000020".to_string(),
4454            dark: "#00000060".to_string(),
4455        },
4456    );
4457    tokens.color.insert(
4458        "code_bg".to_string(),
4459        TokenValue::Adaptive {
4460            light: "#F5F5FA".to_string(),
4461            dark: "#0D0D18".to_string(),
4462        },
4463    );
4464    // Bifrost (Glassmorphism) - Frosted Style
4465    tokens.bifrost.insert(
4466        "blur".to_string(),
4467        TokenValue::Single {
4468            value: "25.0".to_string(),
4469        },
4470    );
4471    tokens.bifrost.insert(
4472        "saturation".to_string(),
4473        TokenValue::Single {
4474            value: "1.2".to_string(),
4475        },
4476    );
4477    tokens.bifrost.insert(
4478        "opacity".to_string(),
4479        TokenValue::Single {
4480            value: "0.65".to_string(),
4481        },
4482    );
4483    // Gungnir (Neon Glow)
4484    tokens.gungnir.insert(
4485        "intensity".to_string(),
4486        TokenValue::Single {
4487            value: "1.0".to_string(),
4488        },
4489    );
4490    tokens.gungnir.insert(
4491        "radius".to_string(),
4492        TokenValue::Single {
4493            value: "15.0".to_string(),
4494        },
4495    );
4496    // Mjolnir (Sharp Geometry)
4497    tokens.mjolnir.insert(
4498        "clip_angle".to_string(),
4499        TokenValue::Single {
4500            value: "12.0".to_string(),
4501        },
4502    );
4503    tokens.mjolnir.insert(
4504        "border_width".to_string(),
4505        TokenValue::Single {
4506            value: "2.0".to_string(),
4507        },
4508    );
4509    // Sleipnir (Spring Animation)
4510    tokens.anim.insert(
4511        "stiffness".to_string(),
4512        TokenValue::Single {
4513            value: "170.0".to_string(),
4514        },
4515    );
4516    tokens.anim.insert(
4517        "damping".to_string(),
4518        TokenValue::Single {
4519            value: "26.0".to_string(),
4520        },
4521    );
4522    tokens.anim.insert(
4523        "mass".to_string(),
4524        TokenValue::Single {
4525            value: "1.0".to_string(),
4526        },
4527    );
4528    // Accessibility
4529    tokens.accessibility.insert(
4530        "reduce_motion".to_string(),
4531        TokenValue::Single {
4532            value: "false".to_string(),
4533        },
4534    );
4535    tokens
4536}
4537/// Environment wrapper for accessing ambient values
4538pub struct Environment<K: EnvKey> {
4539    _marker: std::marker::PhantomData<K>,
4540}
4541impl<K: EnvKey> Default for Environment<K> {
4542    fn default() -> Self {
4543        Self::new()
4544    }
4545}
4546impl<K: EnvKey> Environment<K> {
4547    /// Create a new Environment
4548    pub fn new() -> Self {
4549        Self {
4550            _marker: std::marker::PhantomData,
4551        }
4552    }
4553    /// Get the current value from the environment
4554    pub fn get(&self) -> K::Value {
4555        if let Some(env_store) = ENVIRONMENT.get() {
4556            let env_lock = env_store.lock().unwrap();
4557            if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4558                if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4559                    return typed_val.clone();
4560                } else {
4561                    log::warn!(
4562                        "Environment: Downcast failed for key type {:?}",
4563                        std::any::type_name::<K>()
4564                    );
4565                }
4566            } else {
4567                // Lowered to trace to avoid terminal logging overhead under standard debug runs
4568                log::trace!(
4569                    "Environment: Key not found: {:?}. Returning default.",
4570                    std::any::type_name::<K>()
4571                );
4572            }
4573        } else {
4574            // Lowered to trace to avoid terminal logging overhead under standard debug runs
4575            log::trace!(
4576                "Environment: Store not initialized. Key: {:?}. Returning default.",
4577                std::any::type_name::<K>()
4578            );
4579        }
4580        K::default_value()
4581    }
4582}
4583/// Ambient environment management
4584pub mod env {
4585    /// Insert a value into the environment
4586    pub fn insert<K: super::EnvKey>(value: K::Value) {
4587        let store = super::ENVIRONMENT
4588            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4589        let mut env_map = store.lock().unwrap();
4590        env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4591    }
4592    /// Remove a value from the environment.
4593    pub fn remove<K: super::EnvKey>() {
4594        if let Some(store) = super::ENVIRONMENT.get() {
4595            let mut env_map = store.lock().unwrap();
4596            env_map.remove(&std::any::TypeId::of::<K>());
4597        }
4598    }
4599}
4600/// Geometry modifiers
4601/// Size of the view in logical pixels
4602#[derive(Debug, Clone, Copy, PartialEq)]
4603pub struct Size {
4604    pub width: f32,
4605    pub height: f32,
4606}
4607
4608impl Size {
4609    pub const ZERO: Self = Self {
4610        width: 0.0,
4611        height: 0.0,
4612    };
4613
4614    pub fn new(width: f32, height: f32) -> Self {
4615        Self { width, height }
4616    }
4617}
4618
4619/// Insets for padding
4620#[derive(Debug, Clone, Copy, PartialEq)]
4621pub struct EdgeInsets {
4622    pub top: f32,
4623    pub leading: f32,
4624    pub bottom: f32,
4625    pub trailing: f32,
4626}
4627
4628impl EdgeInsets {
4629    /// Equal insets on all edges
4630    pub fn all(value: f32) -> Self {
4631        Self {
4632            top: value,
4633            leading: value,
4634            bottom: value,
4635            trailing: value,
4636        }
4637    }
4638
4639    /// Vertical insets (top and bottom)
4640    pub fn vertical(value: f32) -> Self {
4641        Self {
4642            top: value,
4643            leading: 0.0,
4644            bottom: value,
4645            trailing: 0.0,
4646        }
4647    }
4648
4649    /// Horizontal insets (leading and trailing)
4650    pub fn horizontal(value: f32) -> Self {
4651        Self {
4652            top: 0.0,
4653            leading: value,
4654            bottom: 0.0,
4655            trailing: value,
4656        }
4657    }
4658}
4659
4660/// Modifier to set the size and alignment constraints of a view.
4661/// This determines the proposal size passed to the child and how the child is aligned
4662/// within the layout rect allocated to the frame.
4663#[derive(Debug, Clone, Copy, PartialEq)]
4664pub struct FrameModifier {
4665    /// Exact width to assign to the child view.
4666    pub width: Option<f32>,
4667    /// Exact height to assign to the child view.
4668    pub height: Option<f32>,
4669    /// Minimum width constraint for the view.
4670    pub min_width: Option<f32>,
4671    /// Maximum width constraint for the view.
4672    pub max_width: Option<f32>,
4673    /// Minimum height constraint for the view.
4674    pub min_height: Option<f32>,
4675    /// Maximum height constraint for the view.
4676    pub max_height: Option<f32>,
4677    /// The alignment strategy for positioning the child view within the frame.
4678    pub alignment: Alignment,
4679}
4680
4681impl Default for FrameModifier {
4682    /// Returns the default frame configuration which has no constraints and center alignment.
4683    fn default() -> Self {
4684        Self::new()
4685    }
4686}
4687
4688impl FrameModifier {
4689    /// Creates a new FrameModifier with all dimensions unspecified and center alignment.
4690    pub fn new() -> Self {
4691        Self {
4692            width: None,
4693            height: None,
4694            min_width: None,
4695            max_width: None,
4696            min_height: None,
4697            max_height: None,
4698            alignment: Alignment::Center,
4699        }
4700    }
4701
4702    /// Sets the fixed width of the frame.
4703    pub fn width(mut self, width: f32) -> Self {
4704        self.width = Some(width);
4705        self
4706    }
4707
4708    /// Sets the fixed height of the frame.
4709    pub fn height(mut self, height: f32) -> Self {
4710        self.height = Some(height);
4711        self
4712    }
4713
4714    /// Sets both the fixed width and height of the frame.
4715    pub fn size(mut self, width: f32, height: f32) -> Self {
4716        self.width = Some(width);
4717        self.height = Some(height);
4718        self
4719    }
4720
4721    /// Sets the minimum width constraint.
4722    pub fn min_width(mut self, min_width: f32) -> Self {
4723        self.min_width = Some(min_width);
4724        self
4725    }
4726
4727    /// Sets the maximum width constraint.
4728    pub fn max_width(mut self, max_width: f32) -> Self {
4729        self.max_width = Some(max_width);
4730        self
4731    }
4732
4733    /// Sets the minimum height constraint.
4734    pub fn min_height(mut self, min_height: f32) -> Self {
4735        self.min_height = Some(min_height);
4736        self
4737    }
4738
4739    /// Sets the maximum height constraint.
4740    pub fn max_height(mut self, max_height: f32) -> Self {
4741        self.max_height = Some(max_height);
4742        self
4743    }
4744
4745    /// Sets the alignment strategy for the child within the frame's layout bounds.
4746    pub fn alignment(mut self, alignment: Alignment) -> Self {
4747        self.alignment = alignment;
4748        self
4749    }
4750}
4751
4752impl ViewModifier for FrameModifier {
4753    /// Wraps the child view in a ModifiedView using this frame modifier.
4754    fn modify<V: View>(self, content: V) -> impl View {
4755        ModifiedView::new(content, self)
4756    }
4757
4758    /// Transforms the layout size proposal offered to the child to comply with frame constraints.
4759    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4760        let w = if let Some(width) = self.width {
4761            Some(width)
4762        } else {
4763            proposal.width.map(|pw| {
4764                pw.clamp(
4765                    self.min_width.unwrap_or(0.0),
4766                    self.max_width.unwrap_or(f32::INFINITY),
4767                )
4768            })
4769        };
4770        let h = if let Some(height) = self.height {
4771            Some(height)
4772        } else {
4773            proposal.height.map(|ph| {
4774                ph.clamp(
4775                    self.min_height.unwrap_or(0.0),
4776                    self.max_height.unwrap_or(f32::INFINITY),
4777                )
4778            })
4779        };
4780        SizeProposal {
4781            width: w,
4782            height: h,
4783        }
4784    }
4785
4786    /// Constraints and transforms the child's resulting size to fit the frame's bounds.
4787    fn transform_size(&self, child_size: Size) -> Size {
4788        let w = if let Some(width) = self.width {
4789            width
4790        } else {
4791            child_size.width.clamp(
4792                self.min_width.unwrap_or(0.0),
4793                self.max_width.unwrap_or(f32::INFINITY),
4794            )
4795        };
4796        let h = if let Some(height) = self.height {
4797            height
4798        } else {
4799            child_size.height.clamp(
4800                self.min_height.unwrap_or(0.0),
4801                self.max_height.unwrap_or(f32::INFINITY),
4802            )
4803        };
4804        Size {
4805            width: w,
4806            height: h,
4807        }
4808    }
4809
4810    /// Renders the frame's child view aligned within the layout rect.
4811    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4812        self.render(renderer, rect);
4813        let child_proposal =
4814            self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4815        let child_size = view.intrinsic_size(renderer, child_proposal);
4816
4817        let mut child_x = rect.x;
4818        let mut child_y = rect.y;
4819
4820        match self.alignment {
4821            Alignment::Leading => {
4822                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4823            }
4824            Alignment::Trailing => {
4825                child_x = rect.x + rect.width - child_size.width;
4826                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4827            }
4828            Alignment::Top => {
4829                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4830            }
4831            Alignment::Bottom => {
4832                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4833                child_y = rect.y + rect.height - child_size.height;
4834            }
4835            Alignment::Center => {
4836                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4837                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4838            }
4839        }
4840
4841        let child_rect = Rect {
4842            x: child_x,
4843            y: child_y,
4844            width: child_size.width,
4845            height: child_size.height,
4846        };
4847
4848        view.render(renderer, child_rect);
4849        self.post_render(renderer, rect);
4850    }
4851}
4852
4853/// Modifier to set the flex weight of a view
4854#[derive(Debug, Clone, Copy, PartialEq)]
4855pub struct FlexModifier {
4856    pub weight: f32,
4857}
4858
4859impl ViewModifier for FlexModifier {
4860    fn modify<V: View>(self, content: V) -> impl View {
4861        ModifiedView::new(content, self)
4862    }
4863
4864    fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4865        self.weight
4866    }
4867}
4868
4869/// Modifier that specifies the column and row placement of a view inside a Grid layout.
4870#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4871pub struct GridPlacementModifier {
4872    /// The grid placement settings containing column/row indexes and spans.
4873    pub placement: GridPlacement,
4874}
4875
4876impl ViewModifier for GridPlacementModifier {
4877    /// Wraps the child view in a ModifiedView using this modifier.
4878    fn modify<V: View>(self, content: V) -> impl View {
4879        ModifiedView::new(content, self)
4880    }
4881
4882    /// Exposes the grid placement metadata to parent layout engines.
4883    fn get_grid_placement(&self) -> Option<GridPlacement> {
4884        Some(self.placement)
4885    }
4886}
4887
4888/// Modifier to render a popover, tooltip, or menu view overlaying an anchored view.
4889/// It supports alignment positioning and outside-click dismissal.
4890#[derive(Clone)]
4891pub struct OverlayModifier {
4892    /// The overlay content view.
4893    pub overlay: AnyView,
4894    /// Where the overlay is aligned relative to the anchored view.
4895    pub alignment: Alignment,
4896    /// Additional offset in logical pixels.
4897    pub offset: [f32; 2],
4898    /// Optional dismissal callback triggered by click-outside events.
4899    pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4900}
4901
4902impl ViewModifier for OverlayModifier {
4903    /// Wraps the child view in a ModifiedView using this overlay modifier.
4904    fn modify<V: View>(self, content: V) -> impl View {
4905        ModifiedView::new(content, self)
4906    }
4907
4908    /// Renders the overlay content positioned above the child view.
4909    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4910        // 1. Render primary anchored view
4911        view.render(renderer, rect);
4912
4913        // 2. Measure overlay content
4914        let overlay_size = self
4915            .overlay
4916            .intrinsic_size(renderer, SizeProposal::unspecified());
4917
4918        // 3. Align overlay rect relative to anchored rect
4919        let mut overlay_x;
4920        let mut overlay_y;
4921
4922        match self.alignment {
4923            Alignment::Leading => {
4924                overlay_x = rect.x - overlay_size.width;
4925                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4926            }
4927            Alignment::Trailing => {
4928                overlay_x = rect.x + rect.width;
4929                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4930            }
4931            Alignment::Top => {
4932                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4933                overlay_y = rect.y - overlay_size.height;
4934            }
4935            Alignment::Bottom => {
4936                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4937                overlay_y = rect.y + rect.height;
4938            }
4939            Alignment::Center => {
4940                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4941                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4942            }
4943        }
4944
4945        overlay_x += self.offset[0];
4946        overlay_y += self.offset[1];
4947
4948        let overlay_rect = Rect {
4949            x: overlay_x,
4950            y: overlay_y,
4951            width: overlay_size.width,
4952            height: overlay_size.height,
4953        };
4954
4955        // 4. Handle click-outside dismissal
4956        if let Some(on_dismiss) = &self.on_dismiss {
4957            let dismiss = on_dismiss.clone();
4958            renderer.register_handler(
4959                "pointerdown",
4960                Arc::new(move |event| {
4961                    if let Event::PointerDown { x, y, .. } = event {
4962                        let click_inside = x >= overlay_rect.x
4963                            && x <= overlay_rect.x + overlay_rect.width
4964                            && y >= overlay_rect.y
4965                            && y <= overlay_rect.y + overlay_rect.height;
4966                        if !click_inside {
4967                            dismiss();
4968                        }
4969                    }
4970                }),
4971            );
4972        }
4973
4974        // 5. Render overlay view
4975        self.overlay.render(renderer, overlay_rect);
4976    }
4977}
4978
4979/// Modifier to offset a view
4980#[derive(Debug, Clone, Copy, PartialEq)]
4981pub struct OffsetModifier {
4982    pub x: f32,
4983    pub y: f32,
4984}
4985
4986impl OffsetModifier {
4987    pub fn new(x: f32, y: f32) -> Self {
4988        Self { x, y }
4989    }
4990}
4991
4992impl ViewModifier for OffsetModifier {
4993    fn modify<V: View>(self, content: V) -> impl View {
4994        ModifiedView::new(content, self)
4995    }
4996}
4997
4998/// Modifier to set the z-index of a view
4999#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5000pub struct ZIndexModifier {
5001    pub z_index: i32,
5002}
5003
5004impl ZIndexModifier {
5005    pub fn new(z_index: i32) -> Self {
5006        Self { z_index }
5007    }
5008}
5009
5010impl ViewModifier for ZIndexModifier {
5011    fn modify<V: View>(self, content: V) -> impl View {
5012        ModifiedView::new(content, self)
5013    }
5014}
5015
5016/// Layout constraints for views
5017#[derive(Debug, Clone, Copy, PartialEq, Default)]
5018pub struct LayoutConstraints {
5019    pub min_width: Option<f32>,
5020    pub max_width: Option<f32>,
5021    pub min_height: Option<f32>,
5022    pub max_height: Option<f32>,
5023}
5024
5025/// Modifier to set layout constraints
5026#[derive(Debug, Clone, Copy, PartialEq)]
5027pub struct LayoutModifier {
5028    pub constraints: LayoutConstraints,
5029}
5030
5031impl LayoutModifier {
5032    pub fn new(constraints: LayoutConstraints) -> Self {
5033        Self { constraints }
5034    }
5035}
5036
5037impl ViewModifier for LayoutModifier {
5038    fn modify<V: View>(self, content: V) -> impl View {
5039        ModifiedView::new(content, self)
5040    }
5041}
5042
5043/// Modifier to handle platform safe areas
5044#[derive(Debug, Clone, Copy, PartialEq)]
5045pub struct SafeAreaModifier {
5046    pub ignores: bool,
5047}
5048
5049impl ViewModifier for SafeAreaModifier {
5050    fn modify<V: View>(self, content: V) -> impl View {
5051        ModifiedView::new(content, self)
5052    }
5053}
5054
5055/// Modifier to add elevation (shadow) to a view
5056#[derive(Debug, Clone, Copy, PartialEq)]
5057pub struct ElevationModifier {
5058    pub level: f32,
5059}
5060
5061impl ViewModifier for ElevationModifier {
5062    fn modify<V: View>(self, content: V) -> impl View {
5063        ModifiedView::new(content, self)
5064    }
5065
5066    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
5067        if self.level > 0.0 {
5068            let radius = self.level * 2.0;
5069            let offset_y = self.level * 0.5;
5070            let shadow_color = [0.0, 0.0, 0.0, 0.3];
5071            renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
5072            view.render(renderer, rect);
5073            renderer.pop_shadow();
5074        } else {
5075            view.render(renderer, rect);
5076        }
5077    }
5078}
5079
5080// Layout subsystem
5081pub mod layout {
5082    use super::*;
5083
5084    /// Key used to identify a cached layout entry.
5085    /// Combines a view hash with a generation counter for cache invalidation.
5086    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5087    pub struct LayoutKey {
5088        pub view_hash: u64,
5089        pub generation: u64,
5090    }
5091
5092    // Layout pass scratch space
5093    pub struct LayoutCache {
5094        pub safe_area: SafeArea,
5095        pub delta_time: f32,
5096        /// Device scale factor for HiDPI / retina snapping. Defaults to 1.0.
5097        pub scale_factor: f32,
5098        size_cache: HashMap<(u64, u32, u32), Size>, // (ViewHash, ProposalW, ProposalH)
5099        /// Monotonically increasing generation counter for cache invalidation.
5100        /// When a view tree changes, bumping the generation causes stale entries
5101        /// to be treated as invalid without eagerly clearing the entire cache.
5102        generation: u64,
5103        /// Opaque pointer to the active layout engine (e.g. Taffy)
5104        pub engine: Option<Box<dyn std::any::Any + Send + Sync>>,
5105        /// Opaque pointer to the active animation orchestrator
5106        pub animators: Option<Box<dyn std::any::Any + Send + Sync>>,
5107        /// Cached previous rects for view transitions
5108        pub previous_rects: HashMap<u64, Rect>,
5109    }
5110
5111    impl Default for LayoutCache {
5112        fn default() -> Self {
5113            Self::new()
5114        }
5115    }
5116
5117    impl LayoutCache {
5118        pub fn new() -> Self {
5119            Self {
5120                safe_area: SafeArea::default(),
5121                delta_time: 0.016,
5122                scale_factor: 1.0,
5123                size_cache: HashMap::new(),
5124                generation: 0,
5125                engine: None,
5126                animators: None,
5127                previous_rects: HashMap::new(),
5128            }
5129        }
5130
5131        /// Returns the current generation counter.
5132        pub fn generation(&self) -> u64 {
5133            self.generation
5134        }
5135
5136        /// Bump the generation counter, logically invalidating all cached entries
5137        /// without eagerly clearing them. Subsequent lookups with the old generation
5138        /// will miss until re-populated.
5139        pub fn invalidate(&mut self) {
5140            self.generation = self.generation.wrapping_add(1);
5141        }
5142
5143        /// Check whether a cached entry for the given key is still valid
5144        /// against the current generation.
5145        pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5146            key.generation == current_gen && key.generation == self.generation
5147        }
5148
5149        pub fn clear(&mut self) {
5150            self.safe_area = SafeArea::default();
5151            self.size_cache.clear();
5152        }
5153
5154        pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5155            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5156            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5157            self.size_cache.get(&(view_hash, pw, ph)).copied()
5158        }
5159
5160        pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5161            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5162            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5163            self.size_cache.insert((view_hash, pw, ph), size);
5164        }
5165
5166        /// Remove all cached size entries for a specific view hash.
5167        pub fn invalidate_view(&mut self, view_hash: u64) {
5168            self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5169        }
5170    }
5171
5172    /// Proposed size from parent view
5173    #[derive(Debug, Clone, Copy, PartialEq)]
5174    pub struct SizeProposal {
5175        pub width: Option<f32>,
5176        pub height: Option<f32>,
5177    }
5178
5179    impl SizeProposal {
5180        pub fn unspecified() -> Self {
5181            Self {
5182                width: None,
5183                height: None,
5184            }
5185        }
5186
5187        pub fn width(width: f32) -> Self {
5188            Self {
5189                width: Some(width),
5190                height: None,
5191            }
5192        }
5193
5194        pub fn height(height: f32) -> Self {
5195            Self {
5196                width: None,
5197                height: Some(height),
5198            }
5199        }
5200
5201        pub fn tight(width: f32, height: f32) -> Self {
5202            Self {
5203                width: Some(width),
5204                height: Some(height),
5205            }
5206        }
5207
5208        pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5209            Self { width, height }
5210        }
5211    }
5212
5213    /// A view that can participate in layout
5214    pub trait LayoutView: Send {
5215        /// Propose a size for this view given the available space
5216        fn size_that_fits(
5217            &self,
5218            proposal: SizeProposal,
5219            subviews: &[&dyn LayoutView],
5220            cache: &mut LayoutCache,
5221        ) -> Size;
5222
5223        /// Place subviews within the given bounds
5224        fn place_subviews(
5225            &self,
5226            bounds: Rect,
5227            subviews: &mut [&mut dyn LayoutView],
5228            cache: &mut LayoutCache,
5229        );
5230
5231        /// Returns the flex weight of this view (default is 0.0, which means fixed/intrinsic)
5232        fn flex_weight(&self) -> f32 {
5233            0.0
5234        }
5235
5236        /// Returns a persistent unique identifier for this view to enable Layout View Transitions.
5237        /// Return 0 (default) to disable layout animations for this node.
5238        fn view_hash(&self) -> u64 {
5239            0
5240        }
5241
5242        /// Return a debug representation of this layout subtree.
5243        /// The `indent` parameter controls the indentation level for nested display.
5244        fn debug_layout(&self, indent: usize) -> String {
5245            let prefix = " ".repeat(indent);
5246            format!("{}LayoutView", prefix)
5247        }
5248    }
5249    /// Edge insets for padding, margins, and safe areas
5250    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5251    pub struct EdgeInsets {
5252        pub top: f32,
5253        pub leading: f32,
5254        pub bottom: f32,
5255        pub trailing: f32,
5256    }
5257
5258    impl EdgeInsets {
5259        pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5260            Self {
5261                top,
5262                leading,
5263                bottom,
5264                trailing,
5265            }
5266        }
5267
5268        pub fn all(value: f32) -> Self {
5269            Self {
5270                top: value,
5271                leading: value,
5272                bottom: value,
5273                trailing: value,
5274            }
5275        }
5276    }
5277
5278    /// SafeArea constraints provided by the platform
5279    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5280    pub struct SafeArea {
5281        pub insets: EdgeInsets,
5282    }
5283
5284    /// SDF Shape definitions for Vili Interaction Paradigm hit-testing.
5285    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5286    pub enum SdfShape {
5287        Rect(Rect),
5288        RoundedRect { rect: Rect, radius: f32 },
5289        Circle { center: [f32; 2], radius: f32 },
5290    }
5291
5292    /// Rectangle in logical pixels
5293    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5294    pub struct Rect {
5295        pub x: f32,
5296        pub y: f32,
5297        pub width: f32,
5298        pub height: f32,
5299    }
5300
5301    impl Rect {
5302        pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5303            Self {
5304                x,
5305                y,
5306                width,
5307                height,
5308            }
5309        }
5310
5311        pub fn inset(&self, amount: f32) -> Self {
5312            Self {
5313                x: self.x + amount,
5314                y: self.y + amount,
5315                width: (self.width - amount * 2.0).max(0.0),
5316                height: (self.height - amount * 2.0).max(0.0),
5317            }
5318        }
5319
5320        pub fn offset(&self, dx: f32, dy: f32) -> Self {
5321            Self {
5322                x: self.x + dx,
5323                y: self.y + dy,
5324                ..*self
5325            }
5326        }
5327
5328        pub fn zero() -> Self {
5329            Self {
5330                x: 0.0,
5331                y: 0.0,
5332                width: 0.0,
5333                height: 0.0,
5334            }
5335        }
5336
5337        pub fn contains(&self, x: f32, y: f32) -> bool {
5338            x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5339        }
5340
5341        pub fn size(&self) -> Size {
5342            Size {
5343                width: self.width,
5344                height: self.height,
5345            }
5346        }
5347
5348        /// Split the rect horizontally into N equal pieces
5349        pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5350            if n == 0 {
5351                return vec![];
5352            }
5353            let item_width = self.width / n as f32;
5354            (0..n)
5355                .map(|i| Rect {
5356                    x: self.x + i as f32 * item_width,
5357                    y: self.y,
5358                    width: item_width,
5359                    height: self.height,
5360                })
5361                .collect()
5362        }
5363
5364        /// Split the rect vertically into N equal pieces
5365        pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5366            if n == 0 {
5367                return vec![];
5368            }
5369            let item_height = self.height / n as f32;
5370            (0..n)
5371                .map(|i| Rect {
5372                    x: self.x,
5373                    y: self.y + i as f32 * item_height,
5374                    width: self.width,
5375                    height: item_height,
5376                })
5377                .collect()
5378        }
5379    }
5380}
5381
5382// Re-export layout items for convenience
5383pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5384// Size and FrameRenderer are pub items in this module; no re-export alias needed.
5385
5386pub mod agents;
5387pub mod animation;
5388pub mod gpu;
5389pub mod material;
5390pub mod runtime;
5391pub mod scene_graph;
5392pub mod sdf_shadow;
5393
5394pub use material::DrawMaterial;
5395pub use scene_graph::{NodeId, bifrost_registry};
5396
5397// Duplicate AssetState removed - original definition at line 67
5398
5399/// AssetManager defines the interface for loading and caching external resources.
5400pub trait AssetManager: Send + Sync {
5401    /// Request an image asset. Returns the current state (Loading, Ready, or Error).
5402    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5403
5404    /// Pre-load an image into the cache.
5405    fn preload_image(&self, url: &str);
5406}
5407
5408/// The phase of a touch or gesture event in its lifecycle.
5409#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5410pub enum TouchPhase {
5411    /// The touch/gesture has just begun.
5412    Began,
5413    /// The touch/gesture is moving.
5414    Moved,
5415    /// The touch/gesture has ended normally.
5416    Ended,
5417    /// The touch/gesture was cancelled (e.g., by the system).
5418    Cancelled,
5419}
5420
5421/// User input event types
5422#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5423pub enum Event {
5424    PointerDown {
5425        x: f32,
5426        y: f32,
5427        button: u32,
5428        proximity_field: f32,
5429        tilt: Option<f32>,
5430        azimuth: Option<f32>,
5431        pressure: Option<f32>,
5432        barrel_rotation: Option<f32>,
5433        pointer_precision: f32,
5434    },
5435    PointerUp {
5436        x: f32,
5437        y: f32,
5438        button: u32,
5439        tilt: Option<f32>,
5440        azimuth: Option<f32>,
5441        pressure: Option<f32>,
5442        barrel_rotation: Option<f32>,
5443        pointer_precision: f32,
5444    },
5445    PointerMove {
5446        x: f32,
5447        y: f32,
5448        proximity_field: f32,
5449        tilt: Option<f32>,
5450        azimuth: Option<f32>,
5451        pressure: Option<f32>,
5452        barrel_rotation: Option<f32>,
5453        pointer_precision: f32,
5454    },
5455    PointerClick {
5456        x: f32,
5457        y: f32,
5458        button: u32,
5459        tilt: Option<f32>,
5460        azimuth: Option<f32>,
5461        pressure: Option<f32>,
5462        barrel_rotation: Option<f32>,
5463        pointer_precision: f32,
5464    },
5465    PointerEnter,
5466    PointerLeave,
5467    /// Mouse wheel / trackpad scroll event.
5468    /// `delta_x` is the horizontal scroll amount, `delta_y` is the vertical scroll amount (positive = scroll down).
5469    PointerWheel {
5470        x: f32,
5471        y: f32,
5472        delta_x: f32,
5473        delta_y: f32,
5474        pointer_precision: f32,
5475    },
5476    /// Double-click event (rapid successive clicks).
5477    PointerDoubleClick {
5478        x: f32,
5479        y: f32,
5480        button: u32,
5481        pointer_precision: f32,
5482    },
5483    /// Drag-and-drop: drag started (pointer moved while button held past threshold).
5484    DragStart {
5485        x: f32,
5486        y: f32,
5487        button: u32,
5488        pointer_precision: f32,
5489    },
5490    /// Drag-and-drop: drag in progress.
5491    DragMove {
5492        x: f32,
5493        y: f32,
5494        pointer_precision: f32,
5495    },
5496    /// Drag-and-drop: drag ended (pointer released).
5497    DragEnd {
5498        x: f32,
5499        y: f32,
5500        pointer_precision: f32,
5501    },
5502    KeyDown {
5503        key: String,
5504    },
5505    KeyUp {
5506        key: String,
5507    },
5508    /// Focus gained by a node.
5509    FocusIn,
5510    /// Focus lost by a node.
5511    FocusOut,
5512    /// Clipboard copy event.
5513    Copy,
5514    /// Clipboard cut event.
5515    Cut,
5516    /// Clipboard paste event with the pasted text content.
5517    Paste(String),
5518    /// Input Method Editor event (e.g. CJK character composition)
5519    Ime(String),
5520    /// Touch began at the given position.
5521    TouchStart {
5522        x: f32,
5523        y: f32,
5524        touch_id: u64,
5525    },
5526    /// Touch moved to a new position.
5527    TouchMove {
5528        x: f32,
5529        y: f32,
5530        touch_id: u64,
5531    },
5532    /// Touch ended at the given position.
5533    TouchEnd {
5534        x: f32,
5535        y: f32,
5536        touch_id: u64,
5537    },
5538    /// Touch cancelled.
5539    TouchCancel {
5540        touch_id: u64,
5541    },
5542    /// Multi-touch pinch gesture.
5543    /// `center` is the gesture anchor point in device-independent pixels.
5544    /// `scale` is the relative pinch scale (>1 = expand, <1 = contract).
5545    /// `velocity` is the instantaneous velocity of the pinch.
5546    /// `phase` indicates the current phase of the gesture lifecycle.
5547    GesturePinch {
5548        center: [f32; 2],
5549        scale: f32,
5550        velocity: f32,
5551        phase: TouchPhase,
5552    },
5553    /// Multi-touch swipe/pan gesture.
5554    /// `direction` is the normalized direction vector [dx, dy].
5555    /// `velocity` is the instantaneous velocity of the swipe.
5556    /// `phase` indicates the current phase of the gesture lifecycle.
5557    GestureSwipe {
5558        direction: [f32; 2],
5559        velocity: f32,
5560        phase: TouchPhase,
5561    },
5562    /// Drag-and-drop: external file dropped onto window.
5563    FileDrop {
5564        x: f32,
5565        y: f32,
5566        path: String,
5567    },
5568}
5569
5570impl Event {
5571    /// Returns the input pointer precision value in physical pixels if applicable.
5572    ///
5573    /// WHY: Used to scale hit-testing bounding boxes for proximity matching.
5574    /// CONTRACT: Mouse pointer inputs return low precision values (close to 0.0px),
5575    /// whereas touch inputs return larger values (e.g., 150.0px) for finger emulation.
5576    pub fn pointer_precision(&self) -> f32 {
5577        match self {
5578            Self::PointerDown {
5579                pointer_precision, ..
5580            }
5581            | Self::PointerUp {
5582                pointer_precision, ..
5583            }
5584            | Self::PointerMove {
5585                pointer_precision, ..
5586            }
5587            | Self::PointerClick {
5588                pointer_precision, ..
5589            }
5590            | Self::PointerWheel {
5591                pointer_precision, ..
5592            }
5593            | Self::PointerDoubleClick {
5594                pointer_precision, ..
5595            }
5596            | Self::DragStart {
5597                pointer_precision, ..
5598            }
5599            | Self::DragMove {
5600                pointer_precision, ..
5601            }
5602            | Self::DragEnd {
5603                pointer_precision, ..
5604            } => *pointer_precision,
5605            _ => 0.0,
5606        }
5607    }
5608
5609    /// Returns the canonical string name of the event for lookup in handler maps.
5610    pub fn name(&self) -> &'static str {
5611        match self {
5612            Self::PointerDown { .. } => "pointerdown",
5613            Self::PointerUp { .. } => "pointerup",
5614            Self::PointerMove { .. } => "pointermove",
5615            Self::PointerClick { .. } => "pointerclick",
5616            Self::PointerEnter => "pointerenter",
5617            Self::PointerLeave => "pointerleave",
5618            Self::PointerWheel { .. } => "pointerwheel",
5619            Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5620            Self::DragStart { .. } => "dragstart",
5621            Self::DragMove { .. } => "dragmove",
5622            Self::DragEnd { .. } => "dragend",
5623            Self::KeyDown { .. } => "keydown",
5624            Self::KeyUp { .. } => "keyup",
5625            Self::FocusIn => "focusin",
5626            Self::FocusOut => "focusout",
5627            Self::Copy => "copy",
5628            Self::Cut => "cut",
5629            Self::Paste(_) => "paste",
5630            Self::Ime(_) => "ime",
5631            Self::TouchStart { .. } => "touchstart",
5632            Self::TouchMove { .. } => "touchmove",
5633            Self::TouchEnd { .. } => "touchend",
5634            Self::TouchCancel { .. } => "touchcancel",
5635            Self::GesturePinch { .. } => "gesturepinch",
5636            Self::GestureSwipe { .. } => "gestureswipe",
5637            Self::FileDrop { .. } => "filedrop",
5638        }
5639    }
5640}
5641
5642/// Response from an event handler
5643#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5644pub enum EventResponse {
5645    Handled,
5646    Ignored,
5647}
5648
5649/// A basic implementation of AssetManager that can be overridden by platform backends.
5650pub struct DefaultAssetManager {
5651    cache: AssetCache,
5652}
5653type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5654
5655impl Default for DefaultAssetManager {
5656    fn default() -> Self {
5657        Self::new()
5658    }
5659}
5660
5661impl DefaultAssetManager {
5662    pub fn new() -> Self {
5663        Self {
5664            cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5665        }
5666    }
5667}
5668
5669impl AssetManager for DefaultAssetManager {
5670    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5671        if let Some(state) = self.cache.load().get(url) {
5672            return state.clone();
5673        }
5674
5675        self.cache.rcu(|map| {
5676            let mut m = (**map).clone();
5677            m.entry(url.to_string()).or_insert(AssetState::Loading);
5678            m
5679        });
5680        AssetState::Loading
5681    }
5682
5683    fn preload_image(&self, _url: &str) {}
5684}
5685
5686use std::future::Future;
5687
5688/// Suspense wrapper for asynchronous state management.
5689/// Integrates with State<T> to provide loading/error/ready states for async operations.
5690pub struct Suspense<T: Clone + Send + Sync + 'static> {
5691    inner: State<AssetState<T>>,
5692}
5693
5694impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5695    fn default() -> Self {
5696        Self::new()
5697    }
5698}
5699
5700impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5701    pub fn new() -> Self {
5702        Self {
5703            inner: State::new(AssetState::Loading),
5704        }
5705    }
5706
5707    pub fn new_async<F>(future: F) -> Self
5708    where
5709        F: Future<Output = Result<T, String>> + Send + 'static,
5710    {
5711        let suspense = Self::new();
5712        let suspense_clone = suspense.clone();
5713
5714        #[cfg(not(target_arch = "wasm32"))]
5715        {
5716            // Try to use an existing tokio runtime, or fallback to a dedicated thread
5717            if let Ok(handle) = tokio::runtime::Handle::try_current() {
5718                handle.spawn(async move {
5719                    let result = future.await;
5720                    match result {
5721                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5722                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5723                    }
5724                });
5725            } else {
5726                std::thread::spawn(move || {
5727                    let rt = tokio::runtime::Builder::new_current_thread()
5728                        .enable_all()
5729                        .build()
5730                        .unwrap();
5731                    rt.block_on(async {
5732                        let result = future.await;
5733                        match result {
5734                            Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5735                            Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5736                        }
5737                    });
5738                });
5739            }
5740        }
5741        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5742        {
5743            wasm_bindgen_futures::spawn_local(async move {
5744                let result = future.await;
5745                match result {
5746                    Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5747                    Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5748                }
5749            });
5750        }
5751
5752        suspense
5753    }
5754
5755    pub fn ready(value: T) -> Self {
5756        Self {
5757            inner: State::new(AssetState::Ready(value)),
5758        }
5759    }
5760
5761    pub fn error(message: impl Into<String>) -> Self {
5762        Self {
5763            inner: State::new(AssetState::Error(message.into())),
5764        }
5765    }
5766
5767    pub fn get(&self) -> AssetState<T> {
5768        self.inner.get()
5769    }
5770
5771    pub fn get_ref(&self) -> AssetState<T> {
5772        self.inner.get()
5773    }
5774
5775    pub fn is_loading(&self) -> bool {
5776        matches!(self.get(), AssetState::Loading)
5777    }
5778
5779    pub fn is_ready(&self) -> bool {
5780        matches!(self.get(), AssetState::Ready(_))
5781    }
5782
5783    pub fn is_error(&self) -> bool {
5784        matches!(self.get(), AssetState::Error(_))
5785    }
5786
5787    pub fn ready_value(&self) -> Option<T> {
5788        match self.get() {
5789            AssetState::Ready(value) => Some(value),
5790            _ => None,
5791        }
5792    }
5793
5794    pub fn error_message(&self) -> Option<String> {
5795        match self.get() {
5796            AssetState::Error(message) => Some(message),
5797            _ => None,
5798        }
5799    }
5800
5801    pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5802        self.inner.subscribe(callback)
5803    }
5804
5805    pub fn inner_state(&self) -> &State<AssetState<T>> {
5806        &self.inner
5807    }
5808}
5809
5810impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5811    fn clone(&self) -> Self {
5812        Self {
5813            inner: self.inner.clone(),
5814        }
5815    }
5816}
5817
5818impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5819    fn from(value: T) -> Self {
5820        Self::ready(value)
5821    }
5822}
5823
5824impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5825    fn from(result: Result<T, String>) -> Self {
5826        match result {
5827            Ok(value) => Self::ready(value),
5828            Err(error) => Self::error(error),
5829        }
5830    }
5831}
5832
5833#[cfg(test)]
5834mod phase1_test;
5835
5836/// Berserker mode states for the rendering pipeline.
5837#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5838pub enum BerserkerMode {
5839    Normal,
5840    Rage,    // Red tint, slight shake
5841    Frenzy,  // Heavy red tint, motion blur, aggressive shake
5842    GodMode, // Golden aura, lightning arcs
5843}
5844
5845/// Seer trait for AI-assisted UI components.
5846/// Allows components to receive "prophecies" (predictions) from an AI backend.
5847pub trait Seer: Send + Sync {
5848    /// Provide a prediction for the next user action or content.
5849    fn predict(&self, context: &str) -> String;
5850    /// Stream real-time "whispers" (transcriptions/intent).
5851    fn whispers(&self) -> Vec<String>;
5852}
5853
5854#[cfg(test)]
5855mod vili_tests {
5856    use super::*;
5857
5858    struct DummyRenderer;
5859    impl ElapsedTime for DummyRenderer {
5860        fn elapsed_time(&self) -> f32 {
5861            0.0
5862        }
5863        fn delta_time(&self) -> f32 {
5864            0.0
5865        }
5866    }
5867    impl Renderer for DummyRenderer {
5868        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5869        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5870        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5871        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5872        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5873        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5874        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5875        fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5876        fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5877            (0.0, 0.0)
5878        }
5879        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5880        fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {
5881        }
5882        fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5883        fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5884        fn pop_transform_3d(&mut self) {}
5885    }
5886
5887    #[test]
5888    fn test_magnetic_warp() {
5889        let renderer = DummyRenderer;
5890        let anchor = Rect {
5891            x: 100.0,
5892            y: 100.0,
5893            width: 50.0,
5894            height: 50.0,
5895        };
5896        // Pointer is near the anchor (distance < 120)
5897        let pointer = [125.0, 50.0];
5898        // distance from center (125, 125) is 75.
5899        // force = (1.0 - 75/120) * strength
5900        let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5901        // It should pull closer to (125, 125), so Y should be > 50
5902        assert!(warp[1] > 50.0);
5903
5904        // Out of range pointer should remain unchanged
5905        let far_pointer = [500.0, 500.0];
5906        let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5907        assert_eq!(far_pointer, far_warp);
5908    }
5909
5910    #[test]
5911    fn test_mani_glow() {
5912        let renderer = DummyRenderer;
5913        let bounds = Rect {
5914            x: 0.0,
5915            y: 0.0,
5916            width: 100.0,
5917            height: 100.0,
5918        };
5919        let pointer_inside = [50.0, 50.0];
5920        let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5921        assert_eq!(glow_max, 1.0);
5922
5923        let pointer_edge = [50.0, -10.0];
5924        let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5925        assert!(glow_partial > 0.0 && glow_partial < 1.0);
5926    }
5927
5928    #[test]
5929    fn test_fafnir_evolve() {
5930        let renderer = DummyRenderer;
5931        let bounds = Rect {
5932            x: 0.0,
5933            y: 0.0,
5934            width: 100.0,
5935            height: 100.0,
5936        };
5937        let pointer_inside = [50.0, 50.0];
5938        let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5939        assert_eq!(scale, 1.2); // Full scale when hovering center
5940    }
5941
5942    #[test]
5943    fn test_undo_manager_basic() {
5944        let mut manager = UndoManager::new(3, 0.5);
5945        let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5946
5947        let v1 = val.clone();
5948        let v2 = val.clone();
5949        manager.push(
5950            "Add",
5951            move || *v1.lock().unwrap() -= 1,
5952            move || *v2.lock().unwrap() += 1,
5953        );
5954
5955        assert!(manager.can_undo());
5956        assert!(!manager.can_redo());
5957
5958        let undo = manager.undo().unwrap();
5959        undo();
5960        assert_eq!(*val.lock().unwrap(), -1);
5961        assert!(!manager.can_undo());
5962        assert!(manager.can_redo());
5963
5964        let redo = manager.redo().unwrap();
5965        redo();
5966        assert_eq!(*val.lock().unwrap(), 0);
5967    }
5968
5969    #[test]
5970    fn test_undo_manager_depth_limit() {
5971        let mut manager = UndoManager::new(2, 0.5);
5972        manager.push("1", || {}, || {});
5973        manager.push("2", || {}, || {});
5974        manager.push("3", || {}, || {});
5975
5976        assert_eq!(manager.stack.len(), 2);
5977        assert_eq!(manager.position, 2);
5978    }
5979
5980    #[test]
5981    fn test_undo_manager_coalescing() {
5982        let mut manager = UndoManager::new(10, 1.0);
5983        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5984
5985        let c = count.clone();
5986        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5987
5988        let c = count.clone();
5989        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5990
5991        assert_eq!(manager.stack.len(), 1);
5992
5993        let undo = manager.undo().unwrap();
5994        undo();
5995        assert_eq!(*count.lock().unwrap(), -2);
5996    }
5997}
5998
5999// =============================================================================
6000// THEME CONTEXT — Thread-local theme access for components
6001// =============================================================================
6002//
6003// Components call `use_theme()` to get the current SemanticColors.
6004// The native renderer sets this via `set_current_theme()` before each frame.
6005// Falls back to dark theme defaults if no theme has been set.
6006//
6007// We store SemanticColors directly (not the full Theme) to avoid depending
6008// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
6009
6010use std::cell::RefCell;
6011
6012thread_local! {
6013    /// Thread-local semantic colors for the current frame.
6014    static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
6015}
6016
6017/// Semantic colors extracted from the theme for use by components.
6018/// This is a standalone type defined in cvkg-core so cvkg-components
6019/// can use it without depending on cvkg-themes.
6020///
6021/// Components should access these via `use_theme()` rather than hardcoding RGBA.
6022pub use color::SemanticColors;
6023
6024/// Set the current semantic colors for this thread.
6025/// Called by the native renderer before each frame.
6026pub fn set_current_theme(colors: color::SemanticColors) {
6027    THEME_CONTEXT.with(|cell| {
6028        *cell.borrow_mut() = Some(colors);
6029    });
6030}
6031
6032/// Clear the current theme. Called after each frame.
6033pub fn clear_current_theme() {
6034    THEME_CONTEXT.with(|cell| {
6035        *cell.borrow_mut() = None;
6036    });
6037}
6038
6039/// Access the current semantic colors from within a component's `render()` method.
6040///
6041/// Returns the colors set by the most recent `set_current_theme()` call.
6042/// Falls back to dark theme defaults if no theme has been set.
6043///
6044/// # Example
6045/// ```no_run
6046/// use cvkg_core::{use_theme, Renderer, Rect};
6047///
6048/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
6049///     let colors = use_theme();
6050///     renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
6051/// }
6052/// ```
6053pub fn use_theme() -> color::SemanticColors {
6054    THEME_CONTEXT.with(|cell| {
6055        cell.borrow()
6056            .clone()
6057            .unwrap_or_else(color::SemanticColors::dark)
6058    })
6059}
6060
6061// =============================================================================
6062// COLOR MODULE — Standalone semantic colors type
6063// =============================================================================
6064//
6065// This module provides `SemanticColors`, a self-contained color palette that
6066// components can use without depending on `cvkg-themes`. The `use_theme()`
6067// function returns the current `SemanticColors` from thread-local storage.
6068
6069pub mod color {
6070    use super::Color;
6071
6072    /// A complete set of semantic colors for UI components.
6073    ///
6074    /// Each color serves a specific role in the UI. Components should reference
6075    /// these semantic roles rather than hardcoding RGBA values.
6076    ///
6077    /// # Example
6078    /// ```no_run
6079    /// use cvkg_core::{use_theme, Renderer, Rect};
6080    ///
6081    /// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
6082    ///     let colors = use_theme();
6083    ///     // Use accent color for the button background
6084    ///     renderer.fill_rounded_rect(rect, 8.0,
6085    ///         [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
6086    /// }
6087    /// ```
6088    #[derive(Debug, Clone)]
6089    pub struct SemanticColors {
6090        /// Primary brand color — used for key interactive elements.
6091        pub primary: Color,
6092        /// Secondary color — used for less prominent interactive elements.
6093        pub secondary: Color,
6094        /// Accent color — used for highlights, focus rings, CTAs.
6095        pub accent: Color,
6096        /// Page/window background color.
6097        pub background: Color,
6098        /// Surface color — used for cards, panels, sheets.
6099        pub surface: Color,
6100        /// Error color — used for destructive actions, error messages.
6101        pub error: Color,
6102        /// Warning color — used for caution indicators.
6103        pub warning: Color,
6104        /// Success color — used for positive feedback.
6105        pub success: Color,
6106        /// Primary text color.
6107        pub text: Color,
6108        /// Dimmed/disabled text color.
6109        pub text_dim: Color,
6110    }
6111
6112    impl SemanticColors {
6113        /// Dark theme semantic colors (default fallback).
6114        pub fn dark() -> Self {
6115            Self {
6116                primary: Color::new(1.0, 0.84, 0.0, 1.0),      // Viking Gold
6117                secondary: Color::new(1.0, 0.0, 1.0, 1.0),     // Magenta Liquid
6118                accent: Color::new(1.0, 0.0, 0.4, 1.0),        // Crimson Flash
6119                background: Color::new(0.02, 0.02, 0.05, 1.0), // Deep Void
6120                surface: Color::new(0.05, 0.05, 0.07, 1.0),    // Tactical Obsidian
6121                error: Color::new(1.0, 0.2, 0.2, 1.0),         // Red
6122                warning: Color::new(1.0, 0.8, 0.0, 1.0),       // Yellow
6123                success: Color::new(0.0, 1.0, 0.5, 1.0),       // Green
6124                text: Color::new(0.95, 0.95, 1.0, 1.0),        // Near-white
6125                text_dim: Color::new(0.6, 0.6, 0.7, 1.0),      // Gray
6126            }
6127        }
6128
6129        /// Light theme semantic colors.
6130        pub fn light() -> Self {
6131            Self {
6132                primary: Color::new(0.35, 0.30, 0.70, 1.0),
6133                secondary: Color::new(0.30, 0.50, 0.30, 1.0),
6134                accent: Color::new(0.30, 0.35, 0.75, 1.0),
6135                background: Color::new(0.97, 0.97, 0.98, 1.0),
6136                surface: Color::new(0.93, 0.93, 0.95, 1.0),
6137                error: Color::new(0.75, 0.15, 0.15, 1.0),
6138                warning: Color::new(0.80, 0.60, 0.0, 1.0),
6139                success: Color::new(0.15, 0.65, 0.30, 1.0),
6140                text: Color::new(0.08, 0.08, 0.10, 1.0),
6141                text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
6142            }
6143        }
6144
6145        /// Convert the accent color semantic color into interactive state colors.
6146        ///
6147        /// This provides hover/active/focus/disabled variants derived from the
6148        /// accent color, matching the pattern that `cvkg-themes::StateColors` uses.
6149        pub fn accent_states(&self) -> InteractiveColorStates {
6150            InteractiveColorStates::from_color(self.accent)
6151        }
6152
6153        /// Convert the primary color into interactive state colors.
6154        pub fn primary_states(&self) -> InteractiveColorStates {
6155            InteractiveColorStates::from_color(self.primary)
6156        }
6157
6158        /// Convert the error color into interactive state colors.
6159        pub fn error_states(&self) -> InteractiveColorStates {
6160            InteractiveColorStates::from_color(self.error)
6161        }
6162
6163        /// Convert the success color into interactive state colors.
6164        pub fn success_states(&self) -> InteractiveColorStates {
6165            InteractiveColorStates::from_color(self.success)
6166        }
6167    }
6168
6169    /// Interactive state colors derived from a single base color.
6170    ///
6171    /// Provides hover/active/focus/disabled variants for any color,
6172    /// derived via simple lightness adjustments in sRGB space.
6173    #[derive(Debug, Clone)]
6174    pub struct InteractiveColorStates {
6175        pub default: Color,
6176        pub hover: Color,
6177        pub active: Color,
6178        pub focus: Color,
6179        pub disabled: Color,
6180        pub focus_ring: Color,
6181    }
6182
6183    impl InteractiveColorStates {
6184        /// Derive interactive state colors from a base sRGB color.
6185        ///
6186        /// Uses simple lightness adjustments:
6187        /// - Hover: +15% lightness
6188        /// - Active: -15% lightness
6189        /// - Focus: same as default
6190        /// - Disabled: 40% opacity
6191        /// - Focus ring: base color at 70% opacity
6192        pub fn from_color(base: Color) -> Self {
6193            Self {
6194                default: base,
6195                hover: base.lighten(0.15),
6196                active: base.darken(0.15),
6197                focus: base,
6198                disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6199                focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6200            }
6201        }
6202
6203        /// Get the color for a specific interactive state.
6204        pub fn color_for(&self, state: InteractiveState) -> Color {
6205            match state {
6206                InteractiveState::Default => self.default,
6207                InteractiveState::Hover => self.hover,
6208                InteractiveState::Active => self.active,
6209                InteractiveState::Focus => self.focus,
6210                InteractiveState::Disabled => self.disabled,
6211            }
6212        }
6213    }
6214
6215    /// Interactive state for a component.
6216    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6217    pub enum InteractiveState {
6218        Default,
6219        Hover,
6220        Active,
6221        Focus,
6222        Disabled,
6223    }
6224}
6225
6226// =============================================================================
6227// USE_STATE HOOK — Local component state with automatic re-render
6228// =============================================================================
6229//
6230// Components call `use_state(id, initial)` to get a `(getter, setter)` pair.
6231// The setter updates the global system state and triggers a re-render.
6232//
6233// This is the minimal state primitive needed for interactive components.
6234// For complex state, use the global `KnowledgeState` directly.
6235
6236/// Local state hook for components.
6237///
6238/// Returns a `(getter, setter)` pair:
6239/// - `getter()` returns the current value of type `T`
6240/// - `setter(value)` updates the value and triggers a re-render
6241///
6242/// The `id` must be unique per component instance (use a hash of the
6243/// component's label or a generated UUID).
6244pub fn use_state<T: Clone + Send + Sync + 'static>(
6245    id: u64,
6246    initial: T,
6247) -> (impl Fn() -> T, impl Fn(T)) {
6248    // Initialize the state if not already present
6249    let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6250    if !already_exists {
6251        update_system_state(|s| {
6252            let mut ns = s.clone();
6253            ns.set_component_state(id, initial.clone());
6254            ns
6255        });
6256    }
6257
6258    let getter = move || -> T {
6259        load_system_state()
6260            .get_component_state::<T>(id)
6261            .map(|arc_lock| {
6262                arc_lock
6263                    .read()
6264                    .ok()
6265                    .map(|guard| (*guard).clone())
6266                    .unwrap_or_else(|| initial.clone())
6267            })
6268            .unwrap_or_else(|| initial.clone())
6269    };
6270
6271    let setter = {
6272        move |value| {
6273            update_system_state(|s| {
6274                let mut ns = s.clone();
6275                ns.set_component_state(id, value);
6276                ns
6277            });
6278        }
6279    };
6280
6281    (getter, setter)
6282}
6283
6284/// Generate a stable hash ID from a string key.
6285///
6286/// Use this to create unique IDs for `use_state` based on component labels
6287/// or other stable identifiers.
6288///
6289/// # Example
6290/// ```no_run
6291/// use cvkg_core::{use_state, use_state_hash};
6292/// let id = use_state_hash("my-checkbox");
6293/// let (value, set_value) = use_state(id, false);
6294/// ```
6295pub fn use_state_hash(key: &str) -> u64 {
6296    use std::hash::{Hash, Hasher};
6297    let mut s = std::collections::hash_map::DefaultHasher::new();
6298    key.hash(&mut s);
6299    s.finish()
6300}
6301
6302// =============================================================================
6303// ACCESSIBILITY PREFERENCES — System accessibility settings
6304// =============================================================================
6305//
6306// Components and the renderer query these to adapt behavior:
6307// - Reduce Motion: disable non-essential animations
6308// - Reduce Transparency: replace glass materials with opaque surfaces
6309// - Increase Contrast: make borders visible, minimum alpha 0.5
6310
6311thread_local! {
6312    /// Thread-local accessibility preferences.
6313    /// Defaults to no restrictions (all false).
6314    static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6315        std::cell::RefCell::new(AccessibilityPreferences::default());
6316}
6317
6318/// System accessibility preferences that components and the renderer must honor.
6319///
6320/// These map to macOS System Settings > Accessibility:
6321/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
6322/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
6323/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
6324#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6325pub struct AccessibilityPreferences {
6326    /// User prefers reduced motion. Animations should be instant or very short.
6327    pub reduce_motion: bool,
6328    /// User prefers reduced transparency. Glass materials should be opaque.
6329    pub reduce_transparency: bool,
6330    /// User prefers increased contrast. Borders must be visible, min alpha 0.5.
6331    pub increase_contrast: bool,
6332}
6333
6334impl AccessibilityPreferences {
6335    /// Detect system accessibility preferences (macOS).
6336    ///
6337    /// On non-macOS platforms, returns defaults (all false).
6338    /// In a production implementation, this would query the OS APIs.
6339    pub fn detect_from_system() -> Self {
6340        #[cfg(target_os = "macos")]
6341        {
6342            // Try to read macOS accessibility preferences via defaults command
6343            let reduce_motion = std::process::Command::new("defaults")
6344                .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6345                .output()
6346                .ok()
6347                .and_then(|o| String::from_utf8(o.stdout).ok())
6348                .map(|s| s.trim() == "1")
6349                .unwrap_or(false);
6350
6351            let reduce_transparency = std::process::Command::new("defaults")
6352                .args([
6353                    "read",
6354                    "-g",
6355                    "com.apple.universalaccess",
6356                    "reduceTransparency",
6357                ])
6358                .output()
6359                .ok()
6360                .and_then(|o| String::from_utf8(o.stdout).ok())
6361                .map(|s| s.trim() == "1")
6362                .unwrap_or(false);
6363
6364            let increase_contrast = std::process::Command::new("defaults")
6365                .args([
6366                    "read",
6367                    "-g",
6368                    "com.apple.universalaccess",
6369                    "increaseContrast",
6370                ])
6371                .output()
6372                .ok()
6373                .and_then(|o| String::from_utf8(o.stdout).ok())
6374                .map(|s| s.trim() == "1")
6375                .unwrap_or(false);
6376
6377            Self {
6378                reduce_motion,
6379                reduce_transparency,
6380                increase_contrast,
6381            }
6382        }
6383        #[cfg(not(target_os = "macos"))]
6384        {
6385            Self::default()
6386        }
6387    }
6388
6389    /// Apply a minimum alpha constraint for increase-contrast mode.
6390    pub fn min_alpha(&self, requested: f32) -> f32 {
6391        if self.increase_contrast {
6392            requested.max(0.5)
6393        } else {
6394            requested
6395        }
6396    }
6397
6398    /// Returns true if glass effects should be replaced with opaque surfaces.
6399    pub fn should_disable_glass(&self) -> bool {
6400        self.reduce_transparency
6401    }
6402
6403    /// Returns true if animations should be instant.
6404    pub fn should_reduce_motion(&self) -> bool {
6405        self.reduce_motion
6406    }
6407
6408    /// Returns true if borders should be made visible.
6409    pub fn should_increase_contrast(&self) -> bool {
6410        self.increase_contrast
6411    }
6412}
6413
6414/// Get the current accessibility preferences for this thread.
6415pub fn accessibility_preferences() -> AccessibilityPreferences {
6416    ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6417}
6418
6419/// Set the accessibility preferences for this thread.
6420///
6421/// The native renderer should call this on startup and when system
6422/// preferences change (via `detect_from_system()`).
6423pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6424    ACCESSIBILITY_PREFS.with(|p| {
6425        *p.borrow_mut() = prefs;
6426    });
6427}
6428
6429// =============================================================================
6430// CLIPBOARD — System clipboard access
6431// =============================================================================
6432
6433/// Trait for clipboard operations.
6434///
6435/// The native renderer implements this via `arboard` on desktop platforms.
6436/// On WASM, it uses the browser Clipboard API.
6437pub trait ClipboardProvider: Send + Sync {
6438    /// Read text from the system clipboard.
6439    fn read_text(&self) -> Option<String>;
6440    /// Write text to the system clipboard.
6441    fn write_text(&self, text: &str);
6442}
6443
6444/// Default clipboard implementation using `arboard`.
6445/// Note: This is only available when the `arboard` feature is enabled.
6446/// The renderer provides the concrete implementation.
6447#[cfg(not(target_arch = "wasm32"))]
6448pub struct SystemClipboard;
6449
6450#[cfg(not(target_arch = "wasm32"))]
6451impl ClipboardProvider for SystemClipboard {
6452    fn read_text(&self) -> Option<String> {
6453        use std::process::Command;
6454        // Fallback: try pbpaste on macOS
6455        Command::new("pbpaste")
6456            .output()
6457            .ok()
6458            .and_then(|o| String::from_utf8(o.stdout).ok())
6459    }
6460
6461    fn write_text(&self, text: &str) {
6462        use std::process::Command;
6463        // Fallback: try pbcopy on macOS
6464        if let Ok(mut child) = Command::new("pbcopy")
6465            .stdin(std::process::Stdio::piped())
6466            .spawn()
6467        {
6468            if let Some(stdin) = child.stdin.as_mut() {
6469                use std::io::Write;
6470                let _ = stdin.write_all(text.as_bytes());
6471            }
6472            let _ = child.wait();
6473        }
6474    }
6475}
6476
6477// =============================================================================
6478// TEXT INPUT — Direction enum for cursor movement
6479// =============================================================================
6480
6481/// Direction for cursor movement in text input.
6482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6483pub enum TextDirection {
6484    Forward,
6485    Backward,
6486    Up,
6487    Down,
6488    LineStart,
6489    LineEnd,
6490    WordForward,
6491    WordBackward,
6492}
6493
6494/// Text input state managed by the renderer.
6495///
6496/// Components don't store this directly — the renderer maintains it
6497/// and components query/modify it through the Renderer trait methods.
6498#[derive(Debug, Clone, Default)]
6499pub struct TextInputState {
6500    /// The full text content.
6501    pub text: String,
6502    /// Cursor position as byte offset into the text.
6503    pub cursor_pos: usize,
6504    /// Selection anchor. If Some, the selection is from anchor to cursor.
6505    /// If None, there is no selection.
6506    pub selection_anchor: Option<usize>,
6507    /// Whether the input is focused (shows cursor, accepts keyboard).
6508    pub focused: bool,
6509    /// Whether the caret is currently visible (for blinking).
6510    pub caret_visible: bool,
6511    /// Last edit timestamp for undo coalescing.
6512    pub last_edit_time: f32,
6513}
6514
6515impl TextInputState {
6516    /// Create a new TextInputState with the given initial text.
6517    pub fn new(text: impl Into<String>) -> Self {
6518        let text = text.into();
6519        let cursor_pos = text.len();
6520        Self {
6521            text,
6522            cursor_pos,
6523            selection_anchor: None,
6524            focused: false,
6525            caret_visible: true,
6526            last_edit_time: 0.0,
6527        }
6528    }
6529
6530    /// Get the selection range as (start, end) byte offsets.
6531    /// Returns None if there is no selection.
6532    pub fn selection_range(&self) -> Option<(usize, usize)> {
6533        self.selection_anchor.map(|anchor| {
6534            if anchor <= self.cursor_pos {
6535                (anchor, self.cursor_pos)
6536            } else {
6537                (self.cursor_pos, anchor)
6538            }
6539        })
6540    }
6541
6542    /// Get the selected text, or empty string if no selection.
6543    pub fn selected_text(&self) -> String {
6544        self.selection_range()
6545            .map(|(start, end)| self.text[start..end].to_string())
6546            .unwrap_or_default()
6547    }
6548
6549    /// Insert text at the current cursor position, replacing any selection.
6550    pub fn insert(&mut self, new_text: &str) {
6551        if let Some((start, end)) = self.selection_range() {
6552            self.text.replace_range(start..end, new_text);
6553            self.cursor_pos = start + new_text.len();
6554        } else {
6555            self.text.insert_str(self.cursor_pos, new_text);
6556            self.cursor_pos += new_text.len();
6557        }
6558        self.selection_anchor = None;
6559    }
6560
6561    /// Delete characters. If there's a selection, delete it.
6562    /// Otherwise delete `count` characters backward (backspace) or forward (delete).
6563    pub fn delete(&mut self, backward: bool, count: usize) -> String {
6564        if let Some((start, end)) = self.selection_range() {
6565            let deleted = self.text[start..end].to_string();
6566            self.text.replace_range(start..end, "");
6567            self.cursor_pos = start;
6568            self.selection_anchor = None;
6569            return deleted;
6570        }
6571
6572        if backward && self.cursor_pos > 0 {
6573            let start = self.cursor_pos.saturating_sub(count);
6574            let deleted = self.text[start..self.cursor_pos].to_string();
6575            self.text.replace_range(start..self.cursor_pos, "");
6576            self.cursor_pos = start;
6577            deleted
6578        } else if !backward && self.cursor_pos < self.text.len() {
6579            let end = (self.cursor_pos + count).min(self.text.len());
6580            let deleted = self.text[self.cursor_pos..end].to_string();
6581            self.text.replace_range(self.cursor_pos..end, "");
6582            deleted
6583        } else {
6584            String::new()
6585        }
6586    }
6587
6588    /// Move the cursor in the given direction.
6589    pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6590        if !extend_selection {
6591            self.selection_anchor = None;
6592        } else if self.selection_anchor.is_none() {
6593            self.selection_anchor = Some(self.cursor_pos);
6594        }
6595
6596        match direction {
6597            TextDirection::Forward if self.cursor_pos < self.text.len() => {
6598                // Move to next character boundary (UTF-8 safe)
6599                let next = self.text[self.cursor_pos..]
6600                    .char_indices()
6601                    .nth(1)
6602                    .map(|(i, _)| self.cursor_pos + i)
6603                    .unwrap_or(self.text.len());
6604                self.cursor_pos = next;
6605            }
6606            TextDirection::Backward if self.cursor_pos > 0 => {
6607                let prev = self.text[..self.cursor_pos]
6608                    .char_indices()
6609                    .next_back()
6610                    .map(|(i, _)| i)
6611                    .unwrap_or(0);
6612                self.cursor_pos = prev;
6613            }
6614            TextDirection::LineStart => {
6615                self.cursor_pos = 0;
6616            }
6617            TextDirection::LineEnd => {
6618                self.cursor_pos = self.text.len();
6619            }
6620            TextDirection::WordForward => {
6621                // Find next word boundary
6622                let rest = &self.text[self.cursor_pos..];
6623                // Skip current word chars
6624                let after_word = rest
6625                    .char_indices()
6626                    .find(|(_, c)| !c.is_alphanumeric())
6627                    .map(|(i, _)| i)
6628                    .unwrap_or(rest.len());
6629                // Skip whitespace
6630                let after_space = rest[after_word..]
6631                    .char_indices()
6632                    .find(|(_, c)| !c.is_whitespace())
6633                    .map(|(i, _)| after_word + i)
6634                    .unwrap_or(rest.len());
6635                self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6636            }
6637            TextDirection::WordBackward => {
6638                let before = &self.text[..self.cursor_pos];
6639                // Skip whitespace going backward
6640                let before_word = before
6641                    .char_indices()
6642                    .rev()
6643                    .find(|(_, c)| !c.is_whitespace())
6644                    .map(|(i, _)| i)
6645                    .unwrap_or(0);
6646                // Skip word chars going backward
6647                let word_start = before[..before_word]
6648                    .char_indices()
6649                    .rev()
6650                    .find(|(_, c)| !c.is_alphanumeric())
6651                    .map(|(i, _)| i)
6652                    .unwrap_or(0);
6653                self.cursor_pos = word_start;
6654            }
6655            _ => {} // Up/Down handled by multi-line components
6656        }
6657
6658        if !extend_selection {
6659            self.selection_anchor = None;
6660        }
6661    }
6662
6663    /// Select all text.
6664    pub fn select_all(&mut self) {
6665        self.cursor_pos = self.text.len();
6666        self.selection_anchor = Some(0);
6667    }
6668
6669    /// Get the byte offset of the cursor.
6670    pub fn cursor_byte_pos(&self) -> usize {
6671        self.cursor_pos
6672    }
6673}
6674
6675/// Action details for interactive buttons inside a notification.
6676#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6677pub struct NotificationAction {
6678    /// Unique identifier of the action.
6679    pub id: String,
6680    /// The text label to display on the action button.
6681    pub title: String,
6682    /// Indicates whether the action performs a destructive task (e.g. Delete).
6683    pub is_destructive: bool,
6684}
6685
6686/// Priority tier of a notification.
6687#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6688pub enum NotificationPriority {
6689    /// Placed silently into the notification center without visual alerts.
6690    Passive,
6691    /// Triggers a visual alert (toast) but does not interrupt focus.
6692    #[default]
6693    Active,
6694    /// Important alert that bypasses standard DND/Focus bounds.
6695    TimeSensitive,
6696}
6697
6698/// A structured notification representation.
6699#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6700pub struct Notification {
6701    /// Unique identifier for this notification.
6702    pub id: String,
6703    /// App or source identifier spawning this notification.
6704    pub app_name: Option<String>,
6705    /// The bold heading/title text.
6706    pub title: String,
6707    /// The detailed descriptive body text.
6708    pub body: String,
6709    /// Optional URI or path to an icon asset.
6710    pub icon: Option<String>,
6711    /// Optional sound identifier to play when posting.
6712    pub sound: Option<String>,
6713    /// Interactive actions available on this notification.
6714    pub actions: Vec<NotificationAction>,
6715    /// Timer duration in seconds after which the toast auto-dismisses.
6716    pub timeout: Option<f32>,
6717    /// Priority level for delivery logic.
6718    pub priority: NotificationPriority,
6719    /// Time (in seconds since renderer startup) when this notification was posted.
6720    pub timestamp: f32,
6721    /// Whether the notification has been dismissed/read.
6722    pub dismissed: bool,
6723}
6724
6725/// Error type indicating a failure in generating or posting a notification.
6726#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6727pub enum NotificationError {
6728    /// Permissions denied.
6729    #[error("Notification permission denied")]
6730    PermissionDenied,
6731    /// Failed to post the notification.
6732    #[error("Failed to post notification")]
6733    PostFailed,
6734}
6735
6736/// State of notification permissions.
6737#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6738pub enum NotificationPermission {
6739    /// Explicitly allowed.
6740    Granted,
6741    /// Explicitly blocked.
6742    Denied,
6743    /// Prompt has not been shown or decided yet.
6744    #[default]
6745    NotDetermined,
6746}
6747
6748/// Core interface for routing and dispatching notification events.
6749pub trait NotificationHandler: Send + Sync {
6750    /// Posts a new notification.
6751    fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6752    /// Dismisses a notification by ID.
6753    fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6754    /// Requests delivery permission.
6755    fn request_permission(&self) -> NotificationPermission;
6756}
6757
6758static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6759    std::sync::atomic::AtomicUsize::new(1);
6760
6761/// Default in-app notification handler that writes state to KnowledgeState.
6762#[derive(Clone, Copy, Debug, Default)]
6763pub struct DefaultNotificationHandler;
6764
6765impl NotificationHandler for DefaultNotificationHandler {
6766    /// Save the notification to the global system state (history) and auto-assign an ID if empty.
6767    fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6768        let mut notif = notification;
6769        if notif.id.is_empty() {
6770            let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6771            notif.id = format!("notif_{}", id);
6772        }
6773        update_system_state(|state| {
6774            let mut new_state = state.clone();
6775            new_state.notifications.push(notif.clone());
6776            new_state
6777        });
6778        Ok(())
6779    }
6780
6781    /// Mark a notification as dismissed/read in the global system state.
6782    fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6783        update_system_state(|state| {
6784            let mut new_state = state.clone();
6785            for notif in &mut new_state.notifications {
6786                if notif.id == id {
6787                    notif.dismissed = true;
6788                }
6789            }
6790            new_state
6791        });
6792        Ok(())
6793    }
6794
6795    /// Returns the permission state (always Granted for internal in-app notifications).
6796    fn request_permission(&self) -> NotificationPermission {
6797        NotificationPermission::Granted
6798    }
6799}
6800
6801static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6802    once_cell::sync::OnceCell::new();
6803
6804/// Sets the global notification handler.
6805pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6806    let _ = NOTIFICATION_HANDLER.set(handler);
6807}
6808
6809/// Gets the global notification handler, fallback to DefaultNotificationHandler.
6810pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6811    NOTIFICATION_HANDLER
6812        .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6813        .clone()
6814}
6815
6816/// Filter mapping name to extension list for a file dialog.
6817#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6818pub struct FileFilter {
6819    /// Friendly name of the filter (e.g. "Images").
6820    pub name: String,
6821    /// List of file extensions (e.g. ["png", "jpg"]).
6822    pub extensions: Vec<String>,
6823}
6824
6825/// The mode/purpose of the file dialog.
6826#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6827pub enum FileDialogMode {
6828    /// Pick a single or multiple files to open.
6829    #[default]
6830    OpenFile,
6831    /// Pick a directory path.
6832    OpenDirectory,
6833    /// Prompt for a location/name to save a file.
6834    SaveFile,
6835}
6836
6837/// Dialog options for picking files or directories.
6838#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6839pub struct FileDialog {
6840    /// Title displayed in the dialog window.
6841    pub title: String,
6842    /// Optional starting directory path.
6843    pub default_path: Option<String>,
6844    /// Extensions used to filter selection.
6845    pub filters: Vec<FileFilter>,
6846    /// Open/save mode.
6847    pub mode: FileDialogMode,
6848    /// Allows selecting multiple files if in OpenFile mode.
6849    pub allow_multiple: bool,
6850}
6851
6852/// Errors returned by the file dialog.
6853#[derive(Debug, thiserror::Error)]
6854pub enum FileDialogError {
6855    /// The user closed the dialog without selecting anything.
6856    #[error("File dialog cancelled")]
6857    Cancelled,
6858    /// An input/output error occurred.
6859    #[error("I/O error: {0}")]
6860    Io(#[from] std::io::Error),
6861    /// Platform-specific error.
6862    #[error("Platform error: {0}")]
6863    Platform(String),
6864}
6865
6866impl FileDialog {
6867    /// Creates a new FileDialog with the given mode.
6868    pub fn new(mode: FileDialogMode) -> Self {
6869        Self {
6870            mode,
6871            ..Default::default()
6872        }
6873    }
6874
6875    /// Sets the dialog title.
6876    pub fn title(mut self, title: impl Into<String>) -> Self {
6877        self.title = title.into();
6878        self
6879    }
6880
6881    /// Adds a file filter.
6882    pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6883        self.filters.push(FileFilter {
6884            name: name.to_string(),
6885            extensions: extensions.iter().map(|s| s.to_string()).collect(),
6886        });
6887        self
6888    }
6889
6890    /// Sets the default starting directory path.
6891    pub fn default_path(mut self, path: impl Into<String>) -> Self {
6892        self.default_path = Some(path.into());
6893        self
6894    }
6895
6896    /// Sets whether selecting multiple files is allowed.
6897    pub fn allow_multiple(mut self, allow: bool) -> Self {
6898        self.allow_multiple = allow;
6899        self
6900    }
6901}
6902
6903#[cfg(not(target_arch = "wasm32"))]
6904impl FileDialog {
6905    /// Pick file(s) or folder based on current mode configuration.
6906    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6907        let mut dialog = rfd::FileDialog::new();
6908        dialog = dialog.set_title(&self.title);
6909        if let Some(path) = &self.default_path {
6910            dialog = dialog.set_directory(path);
6911        }
6912        for filter in &self.filters {
6913            let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6914            dialog = dialog.add_filter(&filter.name, &refs);
6915        }
6916
6917        match self.mode {
6918            FileDialogMode::OpenFile => {
6919                if self.allow_multiple {
6920                    dialog.pick_files().ok_or(FileDialogError::Cancelled)
6921                } else {
6922                    Ok(dialog.pick_file().into_iter().collect())
6923                }
6924            }
6925            FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6926            FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6927        }
6928    }
6929
6930    /// Helper to pick a single file/directory, returning None if cancelled.
6931    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6932        let results = self.pick()?;
6933        Ok(results.into_iter().next())
6934    }
6935}
6936
6937#[cfg(target_arch = "wasm32")]
6938impl FileDialog {
6939    /// Pick is unsupported/mocked on WASM.
6940    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6941        Err(FileDialogError::Platform(
6942            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6943        ))
6944    }
6945
6946    /// Helper to pick a single file/directory, returning None if cancelled.
6947    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6948        Err(FileDialogError::Platform(
6949            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6950        ))
6951    }
6952}
6953
6954/// Error type representing a failure in Document load/save/parse operations.
6955#[derive(Debug, thiserror::Error)]
6956pub enum DocumentError {
6957    /// An input/output error occurred.
6958    #[error("I/O error: {0}")]
6959    Io(#[from] std::io::Error),
6960    /// Failure during deserialization or parsing.
6961    #[error("Parse error: {0}")]
6962    Parse(String),
6963    /// Failure during serialization.
6964    #[error("Serialization error: {0}")]
6965    Serialize(String),
6966}
6967
6968/// A document interface mapping to local filesystem persistence.
6969pub trait Document: Send + Sync {
6970    /// Loads the document from the specified path.
6971    fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6972    where
6973        Self: Sized;
6974
6975    /// Saves the document to the specified path.
6976    fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6977
6978    /// Returns true if the document has unsaved modifications.
6979    fn is_dirty(&self) -> bool;
6980
6981    /// Marks the document as clean/saved.
6982    fn mark_clean(&mut self);
6983}
6984
6985/// Periodic auto-save coordinator for open Documents.
6986pub struct AutoSaveManager {
6987    /// Time interval in seconds between auto-saves.
6988    pub interval: f32,
6989    /// Elapsed timer tracker.
6990    pub timer: f32,
6991    /// Registered open documents under management.
6992    pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6993}
6994
6995impl AutoSaveManager {
6996    /// Creates a new AutoSaveManager with the specified check interval.
6997    pub fn new(interval: f32) -> Self {
6998        Self {
6999            interval,
7000            timer: 0.0,
7001            documents: Vec::new(),
7002        }
7003    }
7004
7005    /// Register a document with its current file path.
7006    pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
7007        self.documents.push((path, doc));
7008    }
7009
7010    /// Advance the timer and auto-save any dirty documents when the interval is reached.
7011    pub fn tick(&mut self, dt: f32) {
7012        self.timer += dt;
7013        if self.timer >= self.interval {
7014            self.timer = 0.0;
7015            for (path, doc) in &mut self.documents {
7016                if doc.is_dirty() {
7017                    match doc.write_to(path) {
7018                        Ok(()) => {
7019                            doc.mark_clean();
7020                            log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
7021                        }
7022                        Err(e) => {
7023                            log::error!(
7024                                "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
7025                                path,
7026                                e
7027                            );
7028                        }
7029                    }
7030                }
7031            }
7032        }
7033    }
7034}
7035
7036// ── Menu Bar API ──────────────────────────────────────────────────────────────
7037
7038/// Keyboard modifier flags used by [`KeyboardShortcut`].
7039///
7040/// On macOS, `cmd` maps to the Command (⌘) key.
7041/// On all other platforms, `cmd` maps to the Control key.
7042/// This is enforced at the renderer level, not here; the data model is OS-agnostic.
7043#[derive(Debug, Clone, PartialEq, Eq, Default)]
7044pub struct Modifiers {
7045    /// Command on macOS, Control on Windows/Linux.
7046    pub cmd: bool,
7047    /// Shift key.
7048    pub shift: bool,
7049    /// Alt/Option key.
7050    pub alt: bool,
7051    /// Control key (distinct from cmd on all platforms).
7052    pub ctrl: bool,
7053}
7054
7055/// A keyboard shortcut binding to a menu action.
7056#[derive(Debug, Clone)]
7057pub struct KeyboardShortcut {
7058    /// The key character or name, e.g. `"s"`, `"z"`, `"Return"`.
7059    pub key: String,
7060    /// The required modifier combination.
7061    pub modifiers: Modifiers,
7062}
7063
7064impl KeyboardShortcut {
7065    /// Convenience constructor: cmd (or ctrl on non-macOS) + `key`.
7066    pub fn cmd(key: impl Into<String>) -> Self {
7067        Self {
7068            key: key.into(),
7069            modifiers: Modifiers {
7070                cmd: true,
7071                ..Default::default()
7072            },
7073        }
7074    }
7075
7076    /// Convenience constructor: cmd+Shift + `key`.
7077    pub fn cmd_shift(key: impl Into<String>) -> Self {
7078        Self {
7079            key: key.into(),
7080            modifiers: Modifiers {
7081                cmd: true,
7082                shift: true,
7083                ..Default::default()
7084            },
7085        }
7086    }
7087}
7088
7089/// A single entry in a [`MenuBar`].
7090///
7091/// Actions hold a callback that is invoked when the user activates the item
7092/// (either via the menu UI or via the associated keyboard shortcut).
7093/// Separators provide visual grouping. Submenus allow hierarchical menus.
7094pub enum MenuItem {
7095    /// An activatable menu entry with an optional shortcut and enabled/disabled state.
7096    Action {
7097        label: String,
7098        shortcut: Option<KeyboardShortcut>,
7099        action: std::sync::Arc<dyn Fn() + Send + Sync>,
7100        enabled: bool,
7101    },
7102    /// A nested submenu.
7103    Submenu { label: String, items: Vec<MenuItem> },
7104    /// A visual separator line between groups of items.
7105    Separator,
7106}
7107
7108impl std::fmt::Debug for MenuItem {
7109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7110        match self {
7111            Self::Action { label, enabled, .. } => f
7112                .debug_struct("Action")
7113                .field("label", label)
7114                .field("enabled", enabled)
7115                .finish(),
7116            Self::Submenu { label, items } => f
7117                .debug_struct("Submenu")
7118                .field("label", label)
7119                .field("items", items)
7120                .finish(),
7121            Self::Separator => write!(f, "Separator"),
7122        }
7123    }
7124}
7125
7126/// A top-level menu bar containing [`MenuItem`]s.
7127///
7128/// The menu bar is a data model only; rendering it into an OS-native menu is
7129/// handled by the platform renderer (`cvkg-render-native`).
7130pub struct MenuBar {
7131    /// Ordered list of top-level menu items.
7132    pub items: Vec<MenuItem>,
7133}
7134
7135impl MenuBar {
7136    /// Create an empty menu bar.
7137    pub fn new() -> Self {
7138        Self { items: Vec::new() }
7139    }
7140
7141    /// Append a menu item to the bar.
7142    pub fn add_item(&mut self, item: MenuItem) {
7143        self.items.push(item);
7144    }
7145
7146    /// Build the standard CVKG menu structure with all conventional shortcuts.
7147    ///
7148    /// The `cmd` modifier maps to ⌘ on macOS and Ctrl on Windows/Linux — this
7149    /// translation is enforced by the renderer, not here.
7150    ///
7151    /// Menus included:
7152    /// - **File**: New, Open, Save, Close
7153    /// - **Edit**: Undo, Redo, Cut, Copy, Paste, Select All, Find
7154    /// - **View**: Zoom In, Zoom Out, Fullscreen
7155    /// - **Window**: Minimize, Zoom, Bring All to Front
7156    /// - **Help**: Search Help
7157    #[allow(clippy::too_many_arguments)]
7158    pub fn standard(
7159        new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7160        open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7161        save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7162        close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7163        quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7164        undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7165        redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7166        cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7167        copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7168        paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7169        select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7170        find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7171    ) -> Self {
7172        let mut bar = Self::new();
7173
7174        // ── File ──────────────────────────────────────────────────────────────
7175        bar.add_item(MenuItem::Submenu {
7176            label: "File".to_string(),
7177            items: vec![
7178                MenuItem::Action {
7179                    label: "New".to_string(),
7180                    shortcut: Some(KeyboardShortcut::cmd("n")),
7181                    action: new_fn,
7182                    enabled: true,
7183                },
7184                MenuItem::Action {
7185                    label: "Open…".to_string(),
7186                    shortcut: Some(KeyboardShortcut::cmd("o")),
7187                    action: open_fn,
7188                    enabled: true,
7189                },
7190                MenuItem::Separator,
7191                MenuItem::Action {
7192                    label: "Save".to_string(),
7193                    shortcut: Some(KeyboardShortcut::cmd("s")),
7194                    action: save_fn,
7195                    enabled: true,
7196                },
7197                MenuItem::Separator,
7198                MenuItem::Action {
7199                    label: "Close".to_string(),
7200                    shortcut: Some(KeyboardShortcut::cmd("w")),
7201                    action: close_fn,
7202                    enabled: true,
7203                },
7204                MenuItem::Separator,
7205                MenuItem::Action {
7206                    label: "Quit".to_string(),
7207                    shortcut: Some(KeyboardShortcut::cmd("q")),
7208                    action: quit_fn,
7209                    enabled: true,
7210                },
7211            ],
7212        });
7213
7214        // ── Edit ──────────────────────────────────────────────────────────────
7215        bar.add_item(MenuItem::Submenu {
7216            label: "Edit".to_string(),
7217            items: vec![
7218                MenuItem::Action {
7219                    label: "Undo".to_string(),
7220                    shortcut: Some(KeyboardShortcut::cmd("z")),
7221                    action: undo_fn,
7222                    enabled: true,
7223                },
7224                MenuItem::Action {
7225                    label: "Redo".to_string(),
7226                    shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7227                    action: redo_fn,
7228                    enabled: true,
7229                },
7230                MenuItem::Separator,
7231                MenuItem::Action {
7232                    label: "Cut".to_string(),
7233                    shortcut: Some(KeyboardShortcut::cmd("x")),
7234                    action: cut_fn,
7235                    enabled: true,
7236                },
7237                MenuItem::Action {
7238                    label: "Copy".to_string(),
7239                    shortcut: Some(KeyboardShortcut::cmd("c")),
7240                    action: copy_fn,
7241                    enabled: true,
7242                },
7243                MenuItem::Action {
7244                    label: "Paste".to_string(),
7245                    shortcut: Some(KeyboardShortcut::cmd("v")),
7246                    action: paste_fn,
7247                    enabled: true,
7248                },
7249                MenuItem::Separator,
7250                MenuItem::Action {
7251                    label: "Select All".to_string(),
7252                    shortcut: Some(KeyboardShortcut::cmd("a")),
7253                    action: select_all_fn,
7254                    enabled: true,
7255                },
7256                MenuItem::Separator,
7257                MenuItem::Action {
7258                    label: "Find…".to_string(),
7259                    shortcut: Some(KeyboardShortcut::cmd("f")),
7260                    action: find_fn,
7261                    enabled: true,
7262                },
7263            ],
7264        });
7265
7266        // ── View ──────────────────────────────────────────────────────────────
7267        // View items carry no application-level callbacks at the model layer;
7268        // zoom and fullscreen are handled by the renderer directly.
7269        let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7270        bar.add_item(MenuItem::Submenu {
7271            label: "View".to_string(),
7272            items: vec![
7273                MenuItem::Action {
7274                    label: "Zoom In".to_string(),
7275                    shortcut: Some(KeyboardShortcut::cmd("=")),
7276                    action: noop.clone(),
7277                    enabled: true,
7278                },
7279                MenuItem::Action {
7280                    label: "Zoom Out".to_string(),
7281                    shortcut: Some(KeyboardShortcut::cmd("-")),
7282                    action: noop.clone(),
7283                    enabled: true,
7284                },
7285                MenuItem::Separator,
7286                MenuItem::Action {
7287                    label: "Toggle Fullscreen".to_string(),
7288                    shortcut: Some(KeyboardShortcut {
7289                        key: "f".to_string(),
7290                        modifiers: Modifiers {
7291                            ctrl: true,
7292                            ..Default::default()
7293                        },
7294                    }),
7295                    action: noop.clone(),
7296                    enabled: true,
7297                },
7298            ],
7299        });
7300
7301        // ── Window ────────────────────────────────────────────────────────────
7302        bar.add_item(MenuItem::Submenu {
7303            label: "Window".to_string(),
7304            items: vec![
7305                MenuItem::Action {
7306                    label: "Minimize".to_string(),
7307                    shortcut: Some(KeyboardShortcut::cmd("m")),
7308                    action: noop.clone(),
7309                    enabled: true,
7310                },
7311                MenuItem::Action {
7312                    label: "Zoom".to_string(),
7313                    shortcut: None,
7314                    action: noop.clone(),
7315                    enabled: true,
7316                },
7317                MenuItem::Separator,
7318                MenuItem::Action {
7319                    label: "Bring All to Front".to_string(),
7320                    shortcut: None,
7321                    action: noop.clone(),
7322                    enabled: true,
7323                },
7324            ],
7325        });
7326
7327        // ── Help ──────────────────────────────────────────────────────────────
7328        bar.add_item(MenuItem::Submenu {
7329            label: "Help".to_string(),
7330            items: vec![MenuItem::Action {
7331                label: "Search Help".to_string(),
7332                shortcut: None,
7333                action: noop,
7334                enabled: true,
7335            }],
7336        });
7337
7338        bar
7339    }
7340}
7341
7342impl Default for MenuBar {
7343    fn default() -> Self {
7344        Self::new()
7345    }
7346}
7347
7348// =============================================================================
7349// LOCALIZATION — Item 12: Localization / Internationalization
7350// =============================================================================
7351// OS-agnostic: works on all platforms. No platform-specific string loading.
7352
7353use std::sync::RwLock;
7354
7355/// Layout direction for UI elements (LTR or RTL).
7356#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7357pub enum Direction {
7358    #[default]
7359    LTR,
7360    RTL,
7361    Auto,
7362}
7363
7364impl Direction {
7365    pub fn is_rtl(self) -> bool {
7366        matches!(self, Direction::RTL)
7367    }
7368}
7369#[derive(Clone, Debug)]
7370pub struct L10nBundle {
7371    pub locale: String,
7372    pub strings: HashMap<String, String>,
7373    pub is_rtl: bool,
7374}
7375
7376impl L10nBundle {
7377    pub fn new(locale: impl Into<String>) -> Self {
7378        Self {
7379            locale: locale.into(),
7380            strings: HashMap::new(),
7381            is_rtl: false,
7382        }
7383    }
7384
7385    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7386        self.strings.insert(key.into(), value.into());
7387        self
7388    }
7389
7390    pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7391        let mut bundle = Self::new(locale);
7392        for line in input.lines() {
7393            let line = line.trim();
7394            if line.is_empty() || line.starts_with("//") {
7395                continue;
7396            }
7397            if let Some(eq_pos) = line.find(" = ") {
7398                let key = line[..eq_pos].trim_matches('"').to_string();
7399                let val = line[eq_pos + 3..]
7400                    .trim_end_matches(';')
7401                    .trim_matches('"')
7402                    .to_string();
7403                bundle.strings.insert(key, val);
7404            }
7405        }
7406        bundle
7407    }
7408    /// Get a translated string by key. Returns the key itself if not found.
7409    pub fn t(&self, key: &str) -> String {
7410        self.strings
7411            .get(key)
7412            .map(|s| s.to_string())
7413            .unwrap_or_else(|| key.to_string())
7414    }
7415
7416    /// Translate with interpolation. Replaces {0}, {1}, etc. with args.
7417    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7418        let mut result = self.t(key);
7419        for (i, arg) in args.iter().enumerate() {
7420            result = result.replace(&format!("{{{}}}", i), arg);
7421        }
7422        result
7423    }
7424}
7425
7426/// Global localization manager.
7427pub struct L10n {
7428    bundles: HashMap<String, L10nBundle>,
7429    current: String,
7430}
7431
7432impl L10n {
7433    pub fn new(default_locale: &str) -> Self {
7434        Self {
7435            bundles: HashMap::new(),
7436            current: default_locale.to_string(),
7437        }
7438    }
7439
7440    pub fn add_bundle(&mut self, bundle: L10nBundle) {
7441        self.bundles.insert(bundle.locale.clone(), bundle);
7442    }
7443
7444    pub fn set_locale(&mut self, locale: &str) {
7445        self.current = locale.to_string();
7446    }
7447    pub fn current_locale(&self) -> &str {
7448        &self.current
7449    }
7450
7451    pub fn is_rtl(&self) -> bool {
7452        self.bundles
7453            .get(self.current.as_str())
7454            .map(|b| b.is_rtl)
7455            .unwrap_or(false)
7456    }
7457
7458    pub fn t(&self, key: &str) -> String {
7459        self.bundles
7460            .get(self.current.as_str())
7461            .map(|b| b.t(key))
7462            .unwrap_or_else(|| key.to_string())
7463    }
7464
7465    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7466        let mut result = self.t(key);
7467        for (i, arg) in args.iter().enumerate() {
7468            result = result.replace(&format!("{{{}}}", i), arg);
7469        }
7470        result
7471    }
7472
7473    pub fn direction(&self) -> Direction {
7474        if self.is_rtl() {
7475            Direction::RTL
7476        } else {
7477            Direction::LTR
7478        }
7479    }
7480}
7481
7482static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7483    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7484
7485pub fn init_l10n(l10n: L10n) {
7486    if let Ok(mut guard) = L10N.write() {
7487        *guard = l10n;
7488    }
7489}
7490
7491pub fn l10n() -> Arc<RwLock<L10n>> {
7492    L10N.clone()
7493}
7494
7495pub fn t(key: &str) -> String {
7496    L10N.read()
7497        .map(|g| g.t(key).to_string())
7498        .unwrap_or_else(|_| key.to_string())
7499}
7500
7501pub fn tf(key: &str, args: &[&str]) -> String {
7502    L10N.read()
7503        .map(|g| g.tf(key, args))
7504        .unwrap_or_else(|_| key.to_string())
7505}
7506
7507pub fn set_locale(locale: &str) {
7508    if let Ok(mut guard) = L10N.write() {
7509        guard.set_locale(locale);
7510    }
7511}
7512
7513pub fn current_locale() -> String {
7514    L10N.read()
7515        .map(|g| g.current_locale().to_string())
7516        .unwrap_or_else(|_| "en".to_string())
7517}
7518
7519pub fn is_rtl() -> bool {
7520    L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7521}
7522
7523// =============================================================================
7524// SYSTEM THEME DETECTION — Dark/Light mode detection
7525// =============================================================================
7526//
7527// OS-agnostic theme detection. Checks the CVKG_THEME environment variable first,
7528// then falls back to dark mode (safe default).
7529//
7530// Platform backends may override this with native OS queries (e.g.,
7531// dark-light crate on desktop, prefers-color-scheme on web).
7532
7533/// The detected system theme.
7534#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7535pub enum SystemTheme {
7536    /// Dark mode (default).
7537    #[default]
7538    Dark,
7539    /// Light mode.
7540    Light,
7541}
7542
7543/// Detect the current system theme.
7544///
7545/// Checks `CVKG_THEME` environment variable first:
7546/// - `"dark"` → `SystemTheme::Dark`
7547/// - `"light"` → `SystemTheme::Light`
7548/// - unset or any other value → `SystemTheme::Dark` (default)
7549///
7550/// Platform backends can call this and override with native detection
7551/// (e.g., `dark-light` crate on desktop, `prefers-color-scheme` on web).
7552pub fn detect_system_theme() -> SystemTheme {
7553    std::env::var("CVKG_THEME")
7554        .ok()
7555        .and_then(|v| match v.as_str() {
7556            "light" => Some(SystemTheme::Light),
7557            "dark" => Some(SystemTheme::Dark),
7558            _ => None,
7559        })
7560        .unwrap_or(SystemTheme::Dark)
7561}
7562
7563// =============================================================================
7564// AUDIO / HAPTIC — Item 14: Spatial Audio / Haptic Feedback
7565// =============================================================================
7566// OS-agnostic: pure trait abstractions. Platform backends via cfg in renderer.
7567
7568pub mod audio_haptic;
7569pub use audio_haptic::{
7570    AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7571    haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7572    set_haptic_engine, sounds,
7573};
7574
7575// =============================================================================
7576// PARALLAX — Depth-based scroll offset system
7577// =============================================================================
7578
7579pub mod parallax;
7580pub use parallax::{DisplayEnvironment, ParallaxModifier, PerformanceContract, Tier3Fallback};