diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 55a4a63..5b77bcb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -31,15 +31,18 @@ jobs: # `--all-targets` so tests and examples are linted too; `-p`-scoped or # target-less clippy silently skips test code. - - name: Clippy (default features — the no_std `ui` build) + # Default features are now empty (`auth` is opt-in), so this lints the + # bare crate; the all-features run below is the one that covers auth. + - name: Clippy (default features) run: cargo clippy --all-targets -- -D warnings - name: Clippy (all features) run: cargo clippy --all-targets --all-features -- -D warnings - # Run BOTH feature sets: the `ui` tests are the only ones in the default - # build, and the `auth` tests only exist behind the feature — running one - # configuration would report a green suite that never touched half the crate. + # Run BOTH feature sets. Every test lives behind `auth`, so the default + # run reports "0 passed; ok" — which is exactly why the all-features run + # below has to exist: a single default-only run would be a green suite + # that executed nothing. - name: Test (default features) run: cargo test diff --git a/README.md b/README.md index 374f6ca..def3286 100644 --- a/README.md +++ b/README.md @@ -12,49 +12,27 @@ OAuth PKCE localhost callback M2M client credentials 0600 credential store - design tokens no_std + PKCE M2M 0600 store

- What it is  ·  Feature tour  ·  Quickstart  ·  Honest status  ·  Migrating  ·  Platform + What it is  ·  Feature tour  ·  Quickstart  ·  Honest status  ·  Platform

--- -> **Every Smoo AI Rust client needs the same three things — so they live in one crate.** Design tokens + the smoo monogram (`ui`), the full Supabase auth story — browser OAuth with PKCE, email+password, session refresh, M2M `client_credentials` — with a shared 0600 on-disk credential store (`auth`), One Rust crate, feature-gated so the bare `ui` build stays `no_std` with zero dependencies. Consumed in production by the [`th` CLI](https://github.com/SmooAI/smooth). **Rust-only today; not yet on crates.io** — npm / NuGet / PyPI siblings are planned, not built. +> **One Supabase auth story, shared by every Smoo AI Rust client.** Browser OAuth with PKCE and a localhost callback, email+password for headless environments, refresh-token rotation, the M2M `client_credentials` grant — and one `0600` on-disk credential store holding user and machine sessions side by side. Consumed in production by the [`th` CLI](https://github.com/SmooAI/smooth). **Rust-only today; not yet on crates.io** — a git dependency is the install path. ## What is this? -A Smoo AI Rust client (smooblue, observability-studio, `th`, `smoo admin`, …) typically needs the same three things: - -1. **Design tokens + monogram** — so the UI looks like Smoo AI. -2. **Auth** — Supabase user OAuth (browser login), email+password, session refresh, AND M2M `client_credentials` grant (service accounts), with one shared on-disk `CredentialsStore`. -3. **LLM access** — exchanging a user session JWT for an org-scoped `llm.smoo.ai` bearer. *(Not built — there is deliberately no feature flag for it yet; see [Honest status](#honest-status).)* - -Each of these has been re-implemented in every consumer at least once. This crate makes them one dependency. It absorbs the standalone [`SmooAI/ui`](https://github.com/SmooAI/ui) crate: `ui` is one module alongside `auth` — same constants, same paths. This repo's `shared/` is the **source of truth** for the design system; SmooAI/ui carries a copy, and a CI gate there fails if the two ever diverge. - -```mermaid -%%{init: {'theme':'base','themeVariables':{ - 'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52', - 'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif', - 'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%% -flowchart LR - subgraph CRATE["smooai-client-shared"] - UI["ui (default)
STYLES · MONOGRAM_SVG · tokens::*
zero deps · no_std"] - AUTH["auth (feature)
oauth · password · refresh · m2m
CredentialsStore (0600)"] - end - AUTH -->|"PKCE localhost callback"| SB[("Supabase
/auth/v1")] - AUTH -->|"client_credentials"| TOK[("auth.smoo.ai/token")] - TH["th CLI
(git dep, features=[auth])"] --> AUTH - APPS["smooblue · desktop apps"] -.->|"can consume"| UI - - classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00; - classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011; - class AUTH warm - class UI,TH teal -``` +The auth plumbing every Smoo AI Rust client needs identically, in one crate instead of re-implemented per app: ---- +1. **Sign a human in** — browser OAuth (PKCE + localhost callback), or email+password where no browser exists. +2. **Keep them signed in** — refresh-token grant with Supabase's rotation handled, plus a renew-ahead window. +3. **Sign a machine in** — the RFC 6749 `client_credentials` grant against `auth.smoo.ai/token`. +4. **Put the credentials somewhere sane** — a `0600` store holding user and M2M sessions together. + +> **This crate used to carry the design system too**, and called itself [`SmooAI/ui`](https://github.com/SmooAI/ui)'s successor. That migration was never finished: nothing ever imported `smooai_client_shared::ui`, while the real design-system consumers (smooblue, observability-studio) depended on `SmooAI/ui` directly. Two copies of the same files is a drift surface, and it had already drifted — this crate spent weeks serving a monogram missing its inner 'S'. The copy is gone. **`SmooAI/ui` owns the design system**; this crate is auth. ## Feature tour @@ -65,7 +43,6 @@ flowchart LR | ♻️ | [**Session refresh**](#-session-refresh) | `refresh_token` grant with rotation handling + a renew-ahead window | | 🤖 | [**M2M `client_credentials`**](#-m2m-client_credentials) | RFC 6749 service-account grant against `auth.smoo.ai/token` | | 💾 | [**`CredentialsStore`**](#-the-credentials-store) | One 0600 on-disk store for user + M2M sessions, side by side | -| 🎨 | [**Design tokens**](#-design-tokens-ui) | The `smooai-ui` surface, verbatim, as the `ui` module | All snippets below are the actual API, verified against `rust/src/`. @@ -134,18 +111,6 @@ if let Some(creds) = store.load()? { } ``` -### 🎨 Design tokens (`ui`) - -The full `smooai-ui` surface, lifted verbatim — canonical OKLCH stylesheet, monogram, and token constants for non-DOM code paths. Zero dependencies, `no_std` when built with only the default `ui` feature: - -```rust -use smooai_client_shared::ui::{STYLES, MONOGRAM_SVG, tokens}; - -let accent = tokens::SMOOAI_GREEN; // "oklch(0.657 0.112 194.8)" -``` - ---- - ## Quickstart **`smooai-client-shared` is not published to crates.io.** Consume it as a git dependency — this is exactly how the [`th` CLI](https://github.com/SmooAI/smooth) consumes it in production (rev-pinned, `features = ["auth"]`): @@ -159,78 +124,36 @@ smooai-client-shared = { git = "https://github.com/SmooAI/client-shared.git", fe | Feature | Adds | Pulls | Status | | --- | --- | --- | --- | -| `ui` (default) | `STYLES`, `MONOGRAM_SVG`, `tokens::*` | nothing — `no_std` | ✅ working | | `auth` | Supabase OAuth + password + refresh, M2M, `CredentialsStore` | `tokio`, `reqwest`, `axum`, `serde`, … | ✅ working, 28 unit tests | -An `llm` feature (JWT → `llm.smoo.ai` org-session exchange, pearl th-f7b20f) is **planned and deliberately absent**. It previously existed as a flag over a six-line doc-comment module: `--features llm` compiled and produced nothing, which is worse than an honest gap. It returns when there is code behind it. - -Run the tests yourself — 34 unit tests across `ui` + `auth` (OAuth callback/PKCE, token rotation, store round-trips, permission bits, token/CSS drift): - -```bash -cd rust && cargo test --all-features -``` - -CI (`.github/workflows/rust.yml`) runs `cargo fmt --check`, `clippy --all-targets -D warnings` and the test suite in **both** feature configurations — the default `no_std` `ui` build and `--all-features` — plus a module-tree check that fails if any `.rs` file is unreachable from a `mod` declaration. - ---- +`auth` is not a default. It is the crate's only surface and its one consumer always asks for it, but the tree it pulls in is heavy enough to be explicit about. ## Honest status | Surface | Status | | --- | --- | -| **Rust `ui`** | ✅ Working — the `tokens` constants are **generated** from `shared/tokens.json` at build time, and cross-checked against `shared/styles.css` in both directions (every token matches its custom property; every colour the CSS declares has a token) | | **Rust `auth`** | ✅ Working — OAuth PKCE localhost-callback (387 LOC), password grant, refresh with rotation, M2M, 0600 `CredentialsStore`; 28 unit tests; consumed by the `th` CLI in production | | **Rust `llm`** | ❌ Not built — no module, no feature flag. Pearl th-f7b20f tracks it | | **crates.io** | ❌ Not published — git dependency is the only install path | -| **npm / NuGet / PyPI** | 📦 Planned, no code — the `src/`, `dotnet/`, `python/` directories in the layout below don't exist yet | +| **npm / NuGet / PyPI** | 📦 Planned, no code — there is no `src/`, `dotnet/` or `python/` directory | ## Layout ``` client-shared/ -├── shared/ # cross-language source of truth -│ ├── styles.css # OKLCH tokens + base component CSS -│ ├── monogram.svg # smoo monogram -│ ├── tokens.json # THE token source — the Rust `tokens` module is generated from it -│ ├── tokens_codegen.rs # generator, run from rust/build.rs -│ └── tokens_css_check.rs # asserts tokens.json <-> styles.css agree, both ways └── rust/ # smooai-client-shared (git dependency; crates.io planned) ├── Cargo.toml └── src/ ├── lib.rs - ├── ui/ # lifted verbatim from smooai-ui └── auth/ # oauth · password · refresh · m2m · storage (feature = "auth") ``` npm (`src/`), NuGet (`dotnet/`), and PyPI (`python/`) packages are roadmap, not directories. -## Migrating from `smooai-ui` - -Both crates are git dependencies (neither is on crates.io — there is no published shim). Migration is a source-level swap: - -```toml -# before -smooai-ui = { git = "https://github.com/SmooAI/ui.git", branch = "main" } - -# after — default features include "ui", same zero-dep no_std tree -smooai-client-shared = { git = "https://github.com/SmooAI/client-shared.git" } -``` - -```rust -// before -use smooai_ui::{STYLES, MONOGRAM_SVG, tokens}; - -// after — every const and sub-module at the same relative path under ui:: -use smooai_client_shared::ui::{STYLES, MONOGRAM_SVG, tokens}; -``` - -The `ui` module is API-compatible with `smooai-ui`: same constants, same paths, and the bare default build inherits the same dependency tree (none). - ## Related repos -- [`SmooAI/ui`](https://github.com/SmooAI/ui) — the original design-system-only crate; [smooblue](https://github.com/SmooAI/smooblue) still consumes it directly. This repo carries the same `ui` surface for clients that also need `auth`. -- [`SmooAI/smooth`](https://github.com/SmooAI/smooth) — the `th` CLI; consumes `client-shared` (`features = ["auth"]`) for login + credential storage. -- `smooblue`, `observability-studio` — Dioxus desktop apps. +- [`SmooAI/ui`](https://github.com/SmooAI/ui) — **the design system**: tokens, base CSS, the smoo monogram. Consumed directly by [smooblue](https://github.com/SmooAI/smooblue) and observability-studio. This crate used to carry a copy; it no longer does. +- [`SmooAI/smooth`](https://github.com/SmooAI/smooth) — the `th` CLI; consumes this crate (`features = ["auth"]`) for login + credential storage. ## 🧩 Part of Smoo AI diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7de4482..87cb906 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,40 +10,27 @@ keywords = ["smooai", "design-system", "auth", "client"] categories = ["gui", "authentication", "config"] readme = "README.md" authors = ["SmooAI "] -# Cargo can't pull files from outside the package root by default; we -# explicitly enumerate the cross-language `shared/` assets we want -# included so `cargo package` produces a self-contained tarball. include = [ "Cargo.toml", "README.md", - "build.rs", "src/**/*.rs", - "../shared/styles.css", - "../shared/monogram.svg", - "../shared/tokens.json", - "../shared/tokens_codegen.rs", - "../shared/tokens_css_check.rs", "../LICENSE", ] [features] -# Default to the lightweight `ui` module so existing smooai-ui -# consumers (smooblue, observability-studio) get the same dependency -# tree they had before — no tokio, no reqwest, still no_std-friendly. -# Opt into `auth` only when needed. -default = ["ui"] -ui = [] +# `auth` is the crate's only surface and its one consumer (the `th` CLI) +# always asks for it — but it stays opt-in rather than default so the heavy +# tree (tokio, reqwest, axum) is never pulled in by accident. +# +# There was a `ui` feature here carrying the design system. Nothing ever +# imported `client_shared::ui`; SmooAI/ui owns that and is what smooblue and +# observability-studio actually depend on. Keeping a second copy behind a +# feature flag only created a drift surface, which had already silently +# shipped a broken monogram. auth = ["dep:tokio", "dep:reqwest", "dep:serde", "dep:serde_json", "dep:url", "dep:axum", "dep:anyhow", "dep:chrono", "dep:dirs-next", "dep:webbrowser", "dep:rand", "dep:base64", "dep:sha2"] -[build-dependencies] -# Build-script only: parses shared/tokens.json to generate the `tokens` -# module. Build dependencies are host-side and never reach consumers, so the -# bare `ui` build stays zero-dependency and `no_std`. -serde_json = "1" - [dependencies] -# Heavy deps gated behind the `auth` feature so the bare `ui` -# build stays no_std-compatible and zero-dep. +# All gated behind the `auth` feature. tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "net", "sync", "time"], optional = true } reqwest = { version = "0.12", features = ["json"], optional = true } serde = { version = "1", features = ["derive"], optional = true } diff --git a/rust/build.rs b/rust/build.rs deleted file mode 100644 index a44518c..0000000 --- a/rust/build.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Generate the `tokens` module from `shared/tokens.json`. -//! -//! The generator itself lives in `shared/tokens_codegen.rs` so SmooAI/ui and -//! SmooAI/client-shared generate identically from the identical input — the -//! `shared/**` drift gate keeps those files byte-for-byte equal. This build -//! script is only the shim that feeds it. - -include!("../shared/tokens_codegen.rs"); - -fn main() { - println!("cargo:rerun-if-changed=../shared/tokens.json"); - println!("cargo:rerun-if-changed=../shared/tokens_codegen.rs"); - - let json = std::fs::read_to_string("../shared/tokens.json").expect("read shared/tokens.json"); - let out = std::path::Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR")).join("tokens.rs"); - std::fs::write(out, generate_tokens_rs(&json)).expect("write tokens.rs"); -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 473713f..8158f27 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,56 +1,41 @@ //! # smooai-client-shared //! -//! Cross-runtime shared library for SmooAI Rust clients — the home for -//! design system primitives, auth flows, and LLM session plumbing that -//! every SmooAI Rust app needs identically. Consumed by `smooblue` -//! (Dioxus desktop), `observability-studio` (Dioxus viewer), `th` and -//! `smoo admin` (the Smooth CLI), and any future Rust client. -//! -//! Replaces the standalone `smooai-ui` crate (which only carried the -//! `ui` slice) by adding an `auth` module behind a feature flag. The bare `default-features = ["ui"]` build stays -//! `no_std`-compatible with zero runtime dependencies — same shape as -//! the old `smooai-ui` so existing consumers don't inherit any new -//! tree. +//! Auth primitives shared across SmooAI's Rust clients — Supabase user +//! OAuth (localhost-callback flow with PKCE), the M2M +//! `client_credentials` grant, refresh-token rotation, and an on-disk +//! `CredentialsStore`. Consumed by `th` and `smoo admin` (the Smooth +//! CLI). //! //! ## Feature flags //! -//! - `ui` (default) — design tokens, base CSS, monogram. Zero deps, -//! `no_std`. -//! - `auth` — Supabase user OAuth (localhost-callback flow), M2M -//! `client_credentials` grant, refresh-token rotation, on-disk -//! `CredentialsStore`. Pulls in `tokio`, `reqwest`, `serde`, `axum`. +//! - `auth` — everything above. Pulls in `tokio`, `reqwest`, `serde`, +//! `axum`. Not a default: the dependency tree is heavy enough that it +//! should be asked for explicitly. +//! +//! ## What happened to the `ui` module +//! +//! This crate used to carry a copy of the design system (tokens, base +//! CSS, monogram) and describe itself as `smooai-ui`'s successor. That +//! migration was never finished, and an org-wide search found **nothing +//! importing `smooai_client_shared::ui`** — every real consumer of the +//! design system depends on [`SmooAI/ui`](https://github.com/SmooAI/ui) +//! directly (smooblue, observability-studio), while this crate's only +//! consumer asks for `auth` and never touched it. +//! +//! Two copies of the same files is a drift surface, and it had already +//! drifted: this crate spent weeks serving a monogram missing its inner +//! 'S' because a fix in SmooAI/ui never crossed. The copy is gone; +//! `SmooAI/ui` owns the design system. //! //! An `llm` feature (JWT → `llm.smoo.ai` org-scoped session exchange) //! is planned under pearl th-f7b20f. It is deliberately **absent** //! rather than stubbed: a feature flag that compiles to an empty //! module advertises a capability that does not exist. It comes back //! when there is something behind it. -//! -//! ## Migrating from `smooai-ui` -//! -//! Replace -//! -//! ```toml -//! smooai-ui = "0.1" -//! ``` -//! -//! with -//! -//! ```toml -//! smooai-client-shared = "0.1" -//! ``` -//! -//! and swap `smooai_ui::` → `smooai_client_shared::ui::` in your -//! imports. Everything in the `ui` module is re-exported at the same -//! path it lived at under `smooai_ui::` (e.g. `STYLES`, -//! `MONOGRAM_SVG`, `tokens::*`). #![cfg_attr(not(feature = "auth"), no_std)] #![doc(html_root_url = "https://docs.rs/smooai-client-shared/0.1.0")] #![warn(missing_docs)] -#[cfg(feature = "ui")] -pub mod ui; - #[cfg(feature = "auth")] pub mod auth; diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs deleted file mode 100644 index a86cfde..0000000 --- a/rust/src/ui/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! # ui — SmooAI design system primitives -//! -//! Lifted verbatim from the standalone `smooai-ui` crate so existing -//! consumers can migrate by changing `smooai_ui::*` → `smooai_client_shared::ui::*`. -//! Pure `pub const &'static str` constants — zero deps, `no_std`-friendly. - -/// Canonical brand stylesheet, sourced from the cross-language -/// `shared/styles.css`. Embed in your app's root component so every -/// consumer sees the same tokens + base component classes. -pub const STYLES: &str = include_str!("../../../shared/styles.css"); - -/// The smoo monogram, as an SVG string with `fill="currentColor"` so -/// the surrounding CSS controls the color. Pair with the -/// `.brand-badge` class (defined in [`STYLES`]) for the gradient pill -/// backdrop. -pub const MONOGRAM_SVG: &str = include_str!("../../../shared/monogram.svg"); - -/// Brand + semantic token *values*, for code paths that need a colour, -/// radius, spacing step, or font stack outside of CSS (custom-painted egui -/// widgets, native menu chrome, chart libraries, etc.). -/// -/// **Generated** at build time from -/// [`shared/tokens.json`](https://github.com/SmooAI/client-shared/blob/main/shared/tokens.json) -/// by `shared/tokens_codegen.rs` — nobody hand-writes these, so a token cannot -/// exist in the design system and be missing here. `shared/tokens_css_check.rs` -/// then asserts each one equals the resolved value of its custom property in -/// [`STYLES`], and that every colour the CSS declares has a token. -pub mod tokens { - include!(concat!(env!("OUT_DIR"), "/tokens.rs")); - - /// The default corner radius, as used by cards and buttons. Retained as an - /// alias of [`RADIUS_MD_PX`] so consumers pinned to the pre-codegen name - /// keep compiling. - pub const RADIUS_PX: u16 = RADIUS_MD_PX; -} - -#[cfg(test)] -mod tests { - extern crate std; - use super::*; - - #[test] - fn styles_is_nonempty() { - assert!(STYLES.len() > 100); - assert!(STYLES.contains("--color-smooai-green")); - } - - #[test] - fn monogram_is_real_svg() { - assert!(MONOGRAM_SVG.starts_with(" CSS cross-check (both directions) lives in `shared/`, so - // SmooAI/client-shared and SmooAI/ui run the identical assertions. - include!("../../../shared/tokens_css_check.rs"); - - #[test] - fn semantic_classes_exist() { - for cls in [ - ".btn", - ".btn--primary", - ".btn--ghost", - ".card", - ".fab", - ".modal__sheet", - ".rail", - ".rail__btn", - ".brand-badge", - ".input", - ".input--lg", - ".input-error", - ".input-hint", - ] { - assert!(STYLES.contains(cls), "missing class {cls}"); - } - } -} diff --git a/shared/monogram.svg b/shared/monogram.svg deleted file mode 100644 index 7fd88a4..0000000 --- a/shared/monogram.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/shared/styles.css b/shared/styles.css deleted file mode 100644 index 65384c2..0000000 --- a/shared/styles.css +++ /dev/null @@ -1,425 +0,0 @@ -/* - * smooai-brand — canonical CSS for SmooAI Rust desktop apps. - * - * Every consumer (smooblue, observability-studio, …) embeds this exact file - * via `smooai_brand::STYLES`. Changes flow through a single PR + version bump - * here, so apps can pin a version and choose when to absorb design updates. - * - * Conventions: - * - Tokens are OKLCH for perceptual consistency across themes. - * - Semantic tokens (--background, --foreground, ...) point at brand tokens. - * - Component classes use BEM-ish naming (.btn, .btn--primary, .card, - * .modal__sheet) so apps can scope their own component CSS without - * clashing. - * - Dark mode is the only mode; brand is dark-first. - * - Hardcode no hex in consumer apps — always reference a token here. - */ - -:root { - /* ===== Brand palette (OKLCH) ===== */ - --color-smooai-orange: oklch(0.769 0.164 71); /* #f49f0a */ - --color-smooai-red: oklch(0.712 0.181 22.4); /* #ff6b6c */ - --color-smooai-green: oklch(0.657 0.112 194.8); /* #00a6a6 */ - --color-smooai-blue-300: oklch(0.803 0.074 230.9); - --color-smooai-blue-400: oklch(0.725 0.102 233.4); - --color-smooai-blue-500: oklch(0.55 0.13 233); - --color-smooai-dark-blue: oklch(0.13 0.043 265.1); /* #020618 */ - --color-smooai-dark-blue-700: oklch(0.303 0.154 265.8); - --color-smooai-dark-blue-850: oklch(0.177 0.074 266); - --color-smooai-white: oklch(0.984 0.003 247.9); - --color-smooai-gray-400: oklch(0.715 0 89.9); - - /* ===== Semantic tokens — components should use these ===== */ - --background: oklch(0.145 0.014 265); - --foreground: var(--color-smooai-white); - --card: oklch(0.205 0.015 265); - --muted: oklch(0.27 0.01 260); - --muted-foreground: var(--color-smooai-gray-400); - --accent: var(--color-smooai-orange); - --border: oklch(0.3 0.008 260); - --input: oklch(0.38 0.006 260); - --ring: oklch(0.657 0.112 194.8); - - /* Sidebar specifics — pulled into a separate slot so apps can have a - different rail tint without losing the panel/card recipe. */ - --sidebar: var(--color-smooai-dark-blue-850); - --sidebar-border: var(--border); - - /* Signature brand gradient — primary CTAs, FABs, monogram badge */ - --gradient-brand: linear-gradient(135deg, - var(--color-smooai-orange) 0%, - var(--color-smooai-red) 100%); - - /* Geometry */ - --radius: 10px; - --radius-sm: 6px; - --radius-lg: 14px; - --radius-xl: 18px; - - /* Spacing rhythm */ - --space-1: 4px; - --space-2: 8px; - --space-3: 12px; - --space-4: 16px; - --space-5: 20px; - --space-6: 24px; - --space-8: 32px; - - /* Typography */ - --font-sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif; - --font-mono: "SF Mono", "JetBrains Mono", "Menlo", "Consolas", ui-monospace, monospace; - - /* Sizes */ - --rail-width: 240px; - --status-bar-height: 36px; -} - -/* =========================================================== - * Reset + base - * =========================================================== */ - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html, -body { - height: 100%; - overflow: hidden; - background: var(--background); - color: var(--foreground); - font-family: var(--font-sans); - font-size: 14px; - line-height: 1.4; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; -} - -button { - font: inherit; - color: inherit; - background: none; - border: 0; - cursor: pointer; -} - -input { - font: inherit; - color: inherit; -} - -/* Counters / IDs — tabular numerals look much tidier in dense lists. */ -.tabular-nums { - font-variant-numeric: tabular-nums; -} - -/* =========================================================== - * Scrollbars - * =========================================================== */ - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} -::-webkit-scrollbar-track { - background: transparent; -} -::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 4px; -} -::-webkit-scrollbar-thumb:hover { - background: var(--color-smooai-dark-blue-700); -} - -/* =========================================================== - * Card surface - * =========================================================== */ - -.card { - background: var(--card); - border: 1px solid var(--border); - border-radius: var(--radius); -} - -.card--elevated { - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55); -} - -.card__row:hover { - background: rgba(255, 255, 255, 0.02); -} - -/* =========================================================== - * Buttons - * =========================================================== */ - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 8px 14px; - border-radius: 8px; - font-size: 13px; - font-weight: 600; - border: 1px solid transparent; - transition: - background-color 120ms ease, - border-color 120ms ease, - opacity 120ms ease, - transform 120ms ease; - cursor: pointer; -} - -.btn:disabled { - opacity: 0.45; - cursor: not-allowed; -} - -.btn--primary { - background: var(--gradient-brand); - color: var(--color-smooai-white); - font-weight: 600; - padding: 10px 16px; - border-radius: 8px; -} -.btn--primary:hover:not(:disabled) { - opacity: 0.92; -} -.btn--primary:active:not(:disabled) { - transform: translateY(1px); -} - -.btn--ghost { - background: transparent; - color: var(--muted-foreground); -} -.btn--ghost:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.04); - color: var(--foreground); -} - -.btn--outline { - background: transparent; - border-color: var(--border); - color: var(--foreground); -} -.btn--outline:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.04); - border-color: var(--ring); -} - -.btn--icon { - padding: 7px; - width: 30px; - height: 30px; -} - -.btn--lg { - padding: 10px 18px; - font-size: 14px; - border-radius: var(--radius-lg); -} - -/* =========================================================== - * Floating Action Button (one per app, bottom-right) - * =========================================================== */ - -.fab { - position: fixed; - right: 24px; - bottom: 24px; - width: 56px; - height: 56px; - border-radius: 50%; - background: var(--gradient-brand); - color: var(--color-smooai-white); - display: inline-flex; - align-items: center; - justify-content: center; - box-shadow: - 0 12px 28px rgba(244, 159, 10, 0.35), - 0 4px 8px rgba(0, 0, 0, 0.25); - transition: - transform 140ms ease, - box-shadow 140ms ease; - z-index: 60; -} -.fab:hover { - transform: translateY(-2px); -} - -/* =========================================================== - * Modal sheet - * =========================================================== */ - -.modal__backdrop { - position: fixed; - inset: 0; - background: rgba(2, 6, 24, 0.6); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - z-index: 50; - display: flex; - align-items: center; - justify-content: center; - padding: var(--space-6); - animation: smooai-fade-in 140ms ease; -} - -.modal__sheet { - width: 100%; - max-width: 560px; - max-width: min(560px, calc(100vw - 48px)); - max-height: calc(100vh - var(--space-8)); - overflow: auto; - padding: 18px 20px 16px; - background: var(--card); - border: 1px solid var(--border); - border-radius: var(--radius-xl); - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55); -} - -@keyframes smooai-fade-in { - from { opacity: 0; } - to { opacity: 1; } -} - -/* =========================================================== - * Sidebar / nav rail (vertical icon strip) - * =========================================================== */ - -.rail { - width: 56px; - flex-shrink: 0; - background: var(--sidebar); - border-right: 1px solid var(--sidebar-border); - display: flex; - flex-direction: column; - align-items: center; - padding: var(--space-3) 0; - gap: var(--space-2); -} - -.rail__btn { - width: 40px; - height: 40px; - border-radius: 8px; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--muted-foreground); - transition: - background-color 120ms ease, - color 120ms ease; -} - -.rail__btn:hover { - background: var(--color-smooai-dark-blue-700); - color: var(--foreground); -} - -.rail__btn--active { - background: var(--color-smooai-blue-500); - color: var(--color-smooai-white); -} - -/* =========================================================== - * Text inputs + textareas - * - * Single canonical form-field style so every consumer's auth screen, - * search bar, settings field, and compose box look like they came from - * the same product. Apply `.input` to ,