aura-gui 0.2.1

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
# Aura UI

Interfaces nativas aceleradas por GPU para **macOS, Windows, Linux, Android e iOS** con un solo código en Rust.
Estilo encadenado con los nombres de Tailwind, componentes tipo shadcn, sombras por capas y animaciones, pensado para
gastar poco: poca RAM, casi nada de CPU en reposo y binarios pequeños.

```bash
cargo add aura-gui
```

Un solo crate con un módulo por capa: `aura_ui::core` (entidades, tareas, geometría, color, animación y capa de
sistema), `::gpu` (renderizador), `::view` (elementos, estilo, layout), `::widgets` (componentes), `::app` (ventana
y bucle) y `::ai` (agentes de IA; feature `ai`, activa por defecto: `default-features = false` la quita).

```rust
use aura_ui::prelude::*;

struct Contador { clics: u32 }

impl Render for Contador {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
        div()
            .size_full().flex_col().items_center().justify_center().gap_4()
            .bg(Token::Canvas).text_color(Token::Text)
            .child(div().p_6().rounded_lg().bg(Token::ElevatedSurface).shadow_md().child(format!("Clics: {}", self.clics)))
            .child(Button::new("Sumar").variant(ButtonVariant::Primary).on_click(cx.click(|this, _| this.clics += 1)))
            .into_element()
    }
}

// Genera `main` en escritorio e iOS y `android_main` en Android.
aura_ui::main!(AppOptions::new("Contador"), |window| window.set_root(Contador { clics: 0 }));
```

## Tailwind

Los nombres son los de Tailwind en `snake_case` y todos son métodos de `Styled`: con `use aura_ui::prelude::*;` basta.
Cada uno solo fija un campo; no hay trabajo en tiempo de ejecución ni memoria extra hasta que se usan.

| Familia | Utilidades |
|---|---|
| Espaciado y tamaño (escala completa, 1 = 4 px) | `p_4 px_2 pt_0_5 m_8 mx_auto gap_x_2 w_64 h_1_2 size_10 min_w_0 max_w_full inset_0 top_px` |
| Flex | `flex_row flex_col_reverse flex_wrap grow shrink_0 flex_1 flex_auto items_baseline justify_evenly self_end content_between` |
| Cuadrícula | `grid_cols_3 grid_rows_2 col_span_2 col_start_2 row_span_3 grid_flow_col` + `gap_*` |
| Color | `blue(500) slate(900) rose(50)` del módulo `palette`, con `.with_alpha(0.5)`; degradados `bg_gradient_to_r(a, b)` |
| Texto | `text_xs … text_9xl font_thin … font_black text_center leading_tight tracking_wide truncate line_clamp_3 uppercase line_through font_mono` |
| Bordes | `border border_t_2 border_x_0 rounded_lg rounded_t_xl rounded_bl_full ring_2 ring_color(..)` |
| Efectos | `shadow_md glow opacity_50` |
| Transformaciones | `rotate_45 neg_rotate_12 scale_105 scale_x_50 translate_x_4 neg_translate_y_1_2 skew_x_6` (giran y escalan desde el centro; no cambian el layout) |
| Filtros | `blur_sm blur_md backdrop_blur_xl backdrop_blur_px(6.0)` |
| Apilado | `z_10 z_50 neg_z_10 z(7)` entre hermanos, también para el ratón |
| Overflow | `overflow_hidden overflow_x_scroll overflow_auto` |
| Estados | `hover active focus group_hover drag_over` |
| Variantes | `sm md lg xl xxl` (según el ancho de la ventana, primero móvil), `dark light` |

```rust
div()
    .flex_col().gap_4().p_6().rounded_xl().border().border_color(palette::slate(200))
    .md(|s| s.flex_row().items_center())
    .dark(|s| s.bg(palette::slate(900)).border_color(palette::slate(700)))
    .child(text("Título").text_2xl().font_semibold().tracking_tight().truncate())
    .child(div().w_1_3().h_10().rounded_lg().bg_gradient_to_r(palette::blue(500), palette::indigo(600)))
```

**Matices**
- `backdrop_blur_*` difumina lo que hay detrás del elemento (cristal esmerilado). `blur_*` difumina el elemento y sus
  hijos, pero se aplica sobre toda su caja: también suaviza el fondo que tenga debajo y no se extiende más allá del borde.
- Un desenfoque dentro de un elemento girado usa el rectángulo que envuelve al giro (no hay desenfoque girado).
- Sin GPU (el rasterizador por CPU) las transformaciones sin giro funcionan; un giro o una inclinación se pinta sin
  girar. Las hitboxes de un elemento transformado son el rectángulo que envuelve a la figura.
- Un desenfoque pinta el frame en una textura propia y suelta su memoria al rato de no usarse.

**Todavía no existe**: `divide-*` y `space-*`, `cursor-*` más allá de los de `CursorStyle`, `select-none`,
`pointer-events-none`, `object-fit`, `mix-blend`, `sticky`, otros filtros (`brightness`, `grayscale`…), valores
arbitrarios (`w-[137px]`; usa `.w(px(137.0))`) y las variantes `disabled:` y `focus-within:`.

## Sombras y animaciones

```rust
div().shadow_lg()                                              // presets xs, sm, md, lg, xl, 2xl
div().shadow(Shadows::one(BoxShadow::new(0., 4., 6.).spread(-1.)).with(BoxShadow::new(0., 12., 24.)))  // capas
div().glow(Token::Accent, 16.0)                                // resplandor de color

let x = cx.transition("panel", if abierto { 180.0 } else { 0.0 }, Animation::new(ms(600)).easing(Easing::SPRING));
let t = cx.animate("pulso", Animation::new(ms(900)).ping_pong());
let c = cx.animate_between("fondo", Animation::new(ms(500)).easing(Easing::STANDARD), rojo, azul);
```

Curvas: `Linear`, `EaseIn`, `EaseOut`, `EaseInOut`, `CubicBezier(..)` (como CSS) y `Spring { bounce }`. Con retraso,
repetición y ida y vuelta. Una animación solo pide frames mientras corre.

## Eficiencia

Medido en macOS arm64 con el ejemplo `contador`, build release:

| | |
|---|---|
| Memoria (huella) | 25 MB |
| CPU en reposo | 0,00 s en 20 s |
| Binario | 7,0 MB (macOS) · 6,1 MB (iOS) · **4,2 MB** (iOS con `opt-level = "s"` y LTO completo) |

Cómo lo logra: el bucle duerme hasta que hay entrada, un temporizador o una animación; no pinta si no se ve; usa la GPU
de bajo consumo con un solo frame en cola; suelta cachés al pasar a segundo plano o ante un aviso de poca memoria; las
sombras son una sola primitiva analítica en el shader, sin pasadas de desenfoque.

Perfil recomendado para tus apps móviles:

```toml
[profile.release]
opt-level = "s"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"
```

## Menos código repetido

Todo viene de `use aura_ui::prelude::*;`, sin importar nada más.

```rust
// Clics: sin `|this: &mut V, _: &ClickEvent, cx|` ni `cx.notify()` (se avisa solo)
.on_click(cx.click(|this, cx| this.pick(name.clone(), cx)))
.on_click(cx.toggle(|t| &mut t.menu_open))          // invierte un bool
.on_click(cx.set(|t| &mut t.tab, Tab::Audio))       // asigna un valor
.on_click(cx.fire(Closed))                          // emite un evento a quien se suscriba

// Segundo plano: sin hilo propio, canal ni `sender.update`; vuelve a la vista y redibuja
cx.job(|cancel| fetch(cancel), |this, result, cx| this.on_fetched(result, cx));
self.search.run(cx, |_| slow_search(q), |this, hits, _| this.hits = hits);   // `Latest`: solo vale la última
cx.job_blocking("login", |cancel| wait_for_browser(cancel), |this, ok, cx| this.on_login(ok, cx));

// Configuración: JSON en ~/.config/<app> (APPDATA en Windows), escritura atómica
let store = Store::new("miapp", "prefs.json").unwrap();
let theme = store.load().str_or("theme", "Aura Dark").to_owned();
store.update(|json| json["theme"] = "Nord".into())?;

// Controles y animación que toda app acaba copiando
div().pressable()                        // mano + hover + active
tool_button(icon, "Deshacer", enabled, cx.click(|t, _| t.undo()))
chip("Alta", selected)                   // etiqueta pulsable
switch(on, "notifs", cx.toggle(|t| &mut t.notifs))
let t = cx.phase("carga", motion);       // segundos; `wave(t, 2.0, 0.0)` da una onda de 0 a 1
let a = cx.fade_in("panel", 200, motion);
```

Por qué cuesta poco: `cx.job` reutiliza el pool de la app (en vez de un hilo del sistema por tarea) y un trabajo
cancelado no llega a tocar la UI; guardar es un temporal más un renombrado; nada de esto despierta el bucle
cuando no hay trabajo.

## Móvil

Ver `docs/movil.md` del repositorio. Resumen: iOS compila para `aarch64-apple-ios` y el simulador; Android
para `aarch64-linux-android` con NativeActivity. Toque, deslizar con inercia, pulsación larga, teclado del sistema y
área segura están implementados. En iOS, añade el manifiesto UIScene al `Info.plist` (plantilla en `docs/ios/Info.plist`;
lo exige el SDK de iOS 27). Probado en el simulador de iPhone; **aún no en un iPhone ni un Android físicos.**

## Licencia

Aura UI tiene **doble licencia**, a tu elección (crates.io muestra la primera; la segunda va incluida en el paquete):

- **AGPL-3.0-only** (`LICENSE-AGPL`, <https://www.gnu.org/licenses/agpl-3.0.html>): gratis. Si la usas, tu app completa
  debe publicarse bajo AGPL-3.0 con su código fuente.
- **Licencia comercial** (`LICENSE-COMMERCIAL.md`): para apps de código cerrado. Regalías del **10 % de los ingresos
  brutos** de cada producto que use Aura UI. Para obtenerla escribe a <camiloghs1@gmail.com>.

Si no tienes la licencia comercial, aplica la AGPL-3.0. Los iconos de `src/widgets/svg` son de Lucide (ISC), con su
aviso en `src/widgets/svg/LICENSE-lucide`.

Copyright (C) 2026 Camilo Grajales Holguín.