commit 0d84eacd6b4af774cfce7a93de3ea26266944ebe
Author: debbster
Date: Wed Sep 2 18:16:44 2026 +0000
Initial commit
diff --git a/.agentsignore b/.agentsignore
new file mode 100644
index 0000000..8a4bb11
--- /dev/null
+++ b/.agentsignore
@@ -0,0 +1,7 @@
+package-lock.json
+Cargo.lock
+/node_modules
+/target
+/dist
+/.idea
+/.git
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c50b5d2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,277 @@
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Rust template
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+dist/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# RustRover
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+### Linux template
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### macOS template
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4b151ab
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..71401a8
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,64 @@
+# AGENTS.md
+
+Iframe Leptos CSR aka "Mont Fort App" craft. Data from generated Matterhorn `*Api`. Theme is `mountainridge`. You implement pages. You do not own codegen, hosting, or the toolchain.
+
+Use the injected workspace tools for git, check, build, issues, and Matterhorn deps. Do not shell out to `git` / `cargo` / `npm` / `trunk`. Commit only if asked (`feat:` / `fix:` / `docs:` / `refactor:`).
+
+## Frozen
+
+`src/codegen/**`, `schemas/`, `read_app_config`, `index.html` config script + `` + asset `copy-dir`, `Router base`, `data-theme="mountainridge"`, `src/styles.css` theme block, `Cargo.toml`.
+
+Missing entity → Matterhorn-search tool → Matterhorn-dep tool, then generate. Still missing → issue, stop. New crate or new CSS tokens → stop. Field not on the struct → migration, not a UI hack.
+
+## Layout
+
+```text
+src/main.rs mount
+src/app.rs App + Shell + routes + nav ← register here
+src/context.rs AppCtx
+src/components/ pages + PageHeader, EmptyView, ErrorView
+src/codegen/ read-only types + *Api
+```
+
+```rust
+let ctx = expect_context::();
+let api = ThingApi::new(ctx.matterhorn_config.clone());
+```
+
+Pages never read `window.__embeddedAppConfig`. Config failure is handled in `App`.
+
+## Page
+
+`src/components/_list.rs` → export in `mod.rs` → one relative `` and one `href="./…"` `` in `src/app.rs`. Do not touch `main.rs`.
+
+Flat siblings under `/`. Nest a `ParentRoute` only for a second ``. `path!("features")`, never `path!("/features")`.
+
+```rust
+let data = LocalResource::new(move || {
+ let api = api.clone();
+ async move { api.fetch_thing(vec![], vec![], HashMap::new(), 50, 0).await }
+});
+
+view! {
+
+ {move || Suspend::new(async move {
+ match data.await {
+ Ok(rows) if rows.is_empty() => view! { }.into_any(),
+ Ok(rows) => view! { /* Option fields only */ }.into_any(),
+ Err(err) => view! { }.into_any(),
+ }
+ })}
+}
+```
+
+Do not add ``. Do not call `.get()` on the resource. `store_*` / forms only if the user asked. Projections = schema names (`createdAt`, not `created_at`).
+
+`view!` text is quoted. `leptos::prelude::*`, not `use leptos::*`. `` takes `attr:class` (or `{..} class`). Brand/nav is not a `.btn`.
+
+## UI
+
+DaisyUI on `mountainridge`: `card`, `table`, `badge`, `menu`. Real `.btn` for submit/delete only. No `tailwind.config.*`. No page CSS. Scroll, if needed, is `min-h-0 flex-1 overflow-y-auto` on the page body — not `h-dvh`, not `html` overflow.
+
+## Done
+
+`check` + `build` clean. Diff = new page + `mod.rs` + `app.rs`. Theme, styles, codegen, Cargo untouched.
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..e38aedd
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "r_155e2f5f_dc_9f_464d_bed2_18120959d71b"
+repository = "http://mridge-gitea:3000/debbster/155e2f5f-dc9f-464d-bed2-18120959d71b.git"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+leptos = { version = "0.8", features = ["csr"] }
+leptos_router = "0.8"
+console_error_panic_hook = "0.1"
+wasm-bindgen = "0.2"
+serde = { version = "1.0", features = ["derive"] }
+matterhorn-client = { version = "0.1" }
+uuid = { version = "1", features = ["serde"] }
+chrono = { version = "0.4", features = ["serde"] }
+anyhow = "1.0"
+serde_json = "1.0.149"
+reqwest = { version = "0.13", features = ["json"] }
+thiserror = "2.0"
+once_cell = "1.21"
+tokio = { version = "1.53", features = ["sync"] }
+gloo-timers = { version = "0.4", features = ["futures"] }
+base64 = "0.23"
+regress = "0.12"
+web-sys = "0.3"
+wasm-bindgen-futures = "0.4"
+js-sys = "0.3"
diff --git a/Trunk.toml b/Trunk.toml
new file mode 100644
index 0000000..0edd0bd
--- /dev/null
+++ b/Trunk.toml
@@ -0,0 +1,6 @@
+[build]
+filehash = false
+public_url = "/crafts/releases/debbster/155e2f5f-dc9f-464d-bed2-18120959d71b/"
+
+[serve]
+addresses = ["0.0.0.0"]
diff --git a/assets/mridge-craft-icon.webp b/assets/mridge-craft-icon.webp
new file mode 100644
index 0000000..815acc2
Binary files /dev/null and b/assets/mridge-craft-icon.webp differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5d55eb7
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ MountainRidge.xyz - Mont Fort App
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..62e2950
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "dependencies": {
+ "@fontsource/geist": "^5.3.0",
+ "daisyui": "^5.5.18",
+ "tailwindcss": "^4.2.1"
+ },
+ "scripts": {
+ "build-craft": "npm install && tailwindcss -i src/styles.css -o assets/styles.css --minify && trunk build --config Trunk.toml",
+ "fmt": "cargo fmt",
+ "check": "cargo fmt && cargo check --all-targets --all-features -q --message-format short --color never",
+ "lint": "cargo clippy --all-targets --all-features -- -D warnings",
+ "test": "cargo test --all-targets --all-features"
+ }
+}
diff --git a/src/app.rs b/src/app.rs
new file mode 100644
index 0000000..016e80e
--- /dev/null
+++ b/src/app.rs
@@ -0,0 +1,97 @@
+use crate::components::{
+ ConfigError, ErrorView, Home, LoadingView, SoftwareFeatureList,
+};
+use crate::context::{read_app_config, AppCtx};
+use leptos::prelude::*;
+use leptos_router::components::{A, Outlet, ParentRoute, Route, Router, Routes};
+use leptos_router::path;
+
+/// Register new pages here: add a `` and a matching nav ``.
+#[component]
+pub fn App() -> impl IntoView {
+ match read_app_config() {
+ None => view! { }.into_any(),
+ Some(config) => {
+ provide_context(config.clone());
+ view! {
+
+ "Not found"
}>
+
+
+
+
+
+ }
+ .into_any()
+ }
+ }
+}
+
+#[component]
+fn Shell() -> impl IntoView {
+ let ctx = expect_context::();
+ let user = ctx.user.clone().unwrap_or_else(|| "Guest".to_string());
+ let logo = ctx.asset_path("mridge-craft-icon.webp");
+
+ view! {
+
+
+
+
+
+
+
+
+
+ }
+ }>
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+}
diff --git a/src/components/config_error.rs b/src/components/config_error.rs
new file mode 100644
index 0000000..43b6dc9
--- /dev/null
+++ b/src/components/config_error.rs
@@ -0,0 +1,21 @@
+use leptos::prelude::*;
+
+#[component]
+pub fn ConfigError() -> impl IntoView {
+ view! {
+
+
+
+
"Configuration unavailable"
+
+ "This craft did not receive APP_CONFIG from the host. "
+ "token and matterhorn_base_url are required."
+
+
+ "Reload the preview after the host has posted the iframe message. Do not continue without config."
+
+
+
+
+ }
+}
diff --git a/src/components/empty_view.rs b/src/components/empty_view.rs
new file mode 100644
index 0000000..d606037
--- /dev/null
+++ b/src/components/empty_view.rs
@@ -0,0 +1,14 @@
+use leptos::prelude::*;
+
+#[component]
+pub fn EmptyView(
+ #[prop(default = "Nothing to show")] title: &'static str,
+ #[prop(default = "No records were returned.")] message: &'static str,
+) -> impl IntoView {
+ view! {
+
+ }
+}
diff --git a/src/components/error_view.rs b/src/components/error_view.rs
new file mode 100644
index 0000000..1468fed
--- /dev/null
+++ b/src/components/error_view.rs
@@ -0,0 +1,16 @@
+use leptos::prelude::*;
+
+#[component]
+pub fn ErrorView(
+ #[prop(default = "Something went wrong")] title: &'static str,
+ message: String,
+) -> impl IntoView {
+ view! {
+
+ }
+}
diff --git a/src/components/home.rs b/src/components/home.rs
new file mode 100644
index 0000000..31f4af7
--- /dev/null
+++ b/src/components/home.rs
@@ -0,0 +1,14 @@
+use crate::components::PageHeader;
+use leptos::prelude::*;
+
+#[component]
+pub fn Home() -> impl IntoView {
+ view! {
+
+
+
+
"Use the navigation to open generated entity pages."
+
+
+ }
+}
diff --git a/src/components/loading_view.rs b/src/components/loading_view.rs
new file mode 100644
index 0000000..5509d1b
--- /dev/null
+++ b/src/components/loading_view.rs
@@ -0,0 +1,11 @@
+use leptos::prelude::*;
+
+#[component]
+pub fn LoadingView(#[prop(default = "Loading…")] label: &'static str) -> impl IntoView {
+ view! {
+
+
+ {label}
+
+ }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
new file mode 100644
index 0000000..bdb2ec2
--- /dev/null
+++ b/src/components/mod.rs
@@ -0,0 +1,13 @@
+mod config_error;
+mod empty_view;
+mod error_view;
+mod home;
+mod loading_view;
+mod page_header;
+
+pub use config_error::ConfigError;
+pub use empty_view::EmptyView;
+pub use error_view::ErrorView;
+pub use home::Home;
+pub use loading_view::LoadingView;
+pub use page_header::PageHeader;
\ No newline at end of file
diff --git a/src/components/page_header.rs b/src/components/page_header.rs
new file mode 100644
index 0000000..92a1ac5
--- /dev/null
+++ b/src/components/page_header.rs
@@ -0,0 +1,20 @@
+use leptos::prelude::*;
+
+#[component]
+pub fn PageHeader(
+ title: &'static str,
+ #[prop(optional)] subtitle: Option<&'static str>,
+ #[prop(optional)] children: Option,
+) -> impl IntoView {
+ view! {
+
+
+
{title}
+ {subtitle.map(|text| view! {
{text}
})}
+
+
+ {children.map(|c| c())}
+
+
+ }
+}
diff --git a/src/context.rs b/src/context.rs
new file mode 100644
index 0000000..ecd06a1
--- /dev/null
+++ b/src/context.rs
@@ -0,0 +1,100 @@
+use matterhorn_client::ClientConfig;
+use wasm_bindgen::JsValue;
+
+#[derive(Clone, Debug)]
+pub struct AppCtx {
+ pub user: Option,
+ pub base_path: String,
+ pub matterhorn_config: ClientConfig,
+}
+
+impl AppCtx {
+ pub fn asset_path(&self, file: &str) -> String {
+ format!("{}assets/{}", self.base_path, file.trim_start_matches('/'))
+ }
+}
+
+fn clog(msg: impl AsRef) {
+ web_sys::console::log_1(&JsValue::from_str(&format!(
+ "[craft config] {}",
+ msg.as_ref()
+ )));
+}
+
+fn js_type_name(value: &JsValue) -> String {
+ if value.is_undefined() {
+ "undefined".into()
+ } else if value.is_null() {
+ "null".into()
+ } else if let Some(s) = value.as_string() {
+ format!("string(len={})", s.len())
+ } else {
+ js_sys::Object::from(value.clone()).to_string().into()
+ }
+}
+
+fn field_string(raw: &JsValue, key: &str) -> Option {
+ let value = js_sys::Reflect::get(raw, &JsValue::from_str(key)).ok()?;
+ clog(format!("{key}: {}", js_type_name(&value)));
+ value.as_string()
+}
+
+/// Reads iframe-injected `window.__embeddedAppConfig`.
+/// Expected payload:
+/// `{ type: "APP_CONFIG", token, user, matterhorn_base_url }`
+pub fn read_app_config() -> Option {
+ clog("read_app_config()");
+
+ let base_path = web_sys::window()
+ .and_then(|w| w.document())
+ .and_then(|d| d.query_selector("base").ok().flatten())
+ .and_then(|el| el.get_attribute("href"))
+ .unwrap_or_else(|| "/".to_string());
+
+ let window = match web_sys::window() {
+ Some(w) => w,
+ None => {
+ clog("window is missing");
+ return None;
+ }
+ };
+
+ let embedded = js_sys::Reflect::get(&window, &"__embeddedAppConfig".into()).ok();
+ let raw = match embedded {
+ Some(value) if !value.is_undefined() && !value.is_null() => value,
+ _ => {
+ clog("no usable __embeddedAppConfig");
+ return None;
+ }
+ };
+
+ let token = match field_string(&raw, "token") {
+ Some(value) if !value.is_empty() => value,
+ Some(_) => {
+ clog("token is empty — host message not applied yet?");
+ return None;
+ }
+ None => {
+ clog("token is not a string");
+ return None;
+ }
+ };
+ let base_url = match field_string(&raw, "matterhorn_base_url") {
+ Some(value) if !value.is_empty() => value,
+ Some(_) => {
+ clog("matterhorn_base_url is empty");
+ return None;
+ }
+ None => {
+ clog("matterhorn_base_url is not a string");
+ return None;
+ }
+ };
+ let user = field_string(&raw, "username");
+
+ Some(AppCtx {
+ user,
+ base_path,
+ matterhorn_config: ClientConfig { base_url, token },
+ })
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..d3ce72e
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,12 @@
+mod app;
+mod context;
+mod components;
+mod codegen;
+
+use app::App;
+use leptos::mount::mount_to_body;
+
+fn main() {
+ console_error_panic_hook::set_once();
+ mount_to_body(App);
+}
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..da01db8
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,315 @@
+@import "@fontsource/geist/200.css";
+@import "@fontsource/geist/300.css";
+@import "@fontsource/geist/400.css";
+@import "@fontsource/geist/500.css";
+@import "@fontsource/geist/600.css";
+@import "tailwindcss";
+
+@plugin "daisyui" {
+ themes: mountainridge --default;
+ darkTheme: mountainridge;
+ base: true;
+ styled: true;
+ utils: true;
+ logs: false;
+}
+
+@plugin "daisyui/theme" {
+ name: "mountainridge";
+ default: true;
+ prefersdark: true;
+ color-scheme: "dark";
+ font-family: "Geist", system-ui, sans-serif;
+ /* Backgrounds — charcoal scale */
+ --color-base-100: oklch(16% 0.02 214);
+ --color-base-200: oklch(13% 0.02 214);
+ --color-base-300: oklch(10% 0.02 214);
+ --color-base-content: oklch(92% 0.005 214);
+
+ /* Primary — sea-grass green rgb(88, 191, 177) */
+ --color-primary: oklch(72% 0.097 183);
+ --color-primary-content: oklch(13% 0.02 214);
+
+ /* Secondary — glacier light blue rgb(94, 150, 159) */
+ --color-secondary: oklch(60% 0.055 205);
+ --color-secondary-content: oklch(13% 0.02 214);
+
+ /* Accent — flamingo pink rgba(253, 117, 127) */
+ --color-accent: oklch(72% 0.155 14); /* flamingo pink */
+ --color-accent-content: oklch(13% 0.02 214);
+
+
+ /* Neutral — charcoal slate */
+ --color-neutral: oklch(28% 0.02 214);
+ --color-neutral-content: oklch(80% 0.01 214);
+
+ /* Semantic */
+ --color-info: oklch(60% 0.055 205);
+ --color-info-content: oklch(13% 0.02 214);
+ --color-success: oklch(72% 0.097 183);
+ --color-success-content: oklch(13% 0.02 214);
+ --color-warning: oklch(68% 0.16 42); /* alpenglow orange */
+ --color-warning-content: oklch(13% 0.02 214);
+ --color-error: oklch(72% 0.155 14); /* flamingo pink */
+ --color-error-content: oklch(13% 0.02 214);
+
+ /* Shape */
+ --radius-selector: 1.5rem;
+ --radius-field: 0.48rem;
+ --radius-box: 0.48rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+}
+
+:root {
+ all: inherit;
+ color: var(--color-base-content); /* DaisyUI's base text color */
+ /* font-family: "Geist", system-ui, sans-serif;*/
+ /* creating the perfect shadow according to twitter/X
+ - https://codepen.io/jh3y/pen/yLWgjpd
+ - https://x.com/jh3yy/status/1796208073830232574
+ */
+ --tint: 214;
+ --alpha: 4;
+ --base: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
+ --shade: hsl(from var(--base) calc(h + 8) 25 calc(l - 5));
+
+ --perfect-shadow: 0 0 0 1px var(--base),
+ 0 1px 1px -0.5px var(--shade),
+ 0 3px 3px -1.5px var(--shade),
+ 0 6px 6px -3px var(--shade),
+ 0 12px 12px -6px var(--base),
+ 0 24px 24px -12px var(--base);
+
+
+ --shade-active: hsla(173, 41%, 53%, 0.66);
+ --base-active: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
+
+ --perfect-shadow-active: 0 0 0 1px var(--base-active),
+ 0 1px 1px -0.5px var(--shade-active),
+ 0 3px 3px -1.5px var(--shade-active),
+ 0 6px 6px -3px var(--shade-active),
+ 0 12px 12px -6px var(--base-active),
+ 0 24px 24px -12px var(--base-active);
+
+ --perfect-shadow-thin-active: 0 0 0 1px var(--base-active),
+ 0 1px 2px -0.5px var(--shade-active),
+ 0 2px 4px -1px var(--shade-active),
+ 0 4px 8px -2px var(--shade-active);
+
+ --shade-active-warn: rgba(253, 117, 127, 0.66);
+ --perfect-shadow-warn: 0 0 0 1px var(--shade-active-warn),
+ 0 1px 1px -0.5px var(--shade-active-warn),
+ 0 3px 3px -1.5px var(--shade-active-warn),
+ 0 6px 6px -3px var(--shade-active-warn),
+ 0 12px 12px -6px var(--base-active),
+ 0 24px 24px -12px var(--base-active);
+
+
+ --base-thin: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
+ --shade-thin: hsl(from var(--base-thin) calc(h + 8) 25 calc(l - 5));
+
+ --perfect-shadow-thin: 0 0 0 1px var(--base-thin),
+ 0 1px 2px -0.5px var(--shade-thin),
+ 0 3px 6px -1px var(--shade-thin);
+ --perfect-shadow-light: 0px 6px 20px -4px rgba(0, 0, 0, 0.40), 0px 0px 30px -8px rgba(123, 94, 248, 0.10);
+ /* === DARK BACKGROUND SHADOWS === */
+
+ /* Tinted with glacier blue for atmosphere */
+ --tint-dark: 205;
+ --alpha-dark: 30;
+
+ --base-dark: hsl(var(--tint-dark) 60% 60% / calc(var(--alpha-dark) * 1%));
+ --shade-dark: hsl(from var(--base-dark) calc(h + 8) 70 calc(l + 10));
+
+ /* Full shadow — for cards, modals, elevated surfaces */
+ --perfect-shadow-dark: 0 0 0 1px hsl(205 40% 50% / 15%),
+ 0 1px 1px -0.5px var(--base-dark),
+ 0 3px 3px -1.5px var(--base-dark),
+ 0 6px 6px -3px var(--shade-dark),
+ 0 12px 12px -6px var(--shade-dark),
+ 0 24px 24px -12px var(--base-dark);
+
+ /* Thin shadow — for buttons, inputs, small elements */
+ --perfect-shadow-thin-dark: 0 0 0 1px hsl(205 40% 50% / 12%),
+ 0 1px 2px -0.5px var(--base-dark),
+ 0 3px 6px -1px var(--shade-dark);
+
+ /* Active/focus state — sea-grass green glow */
+ --shade-active-dark: hsla(183, 35%, 55%, 0.45); /* sea-grass green */
+
+ --perfect-shadow-thin-active-dark: 0 0 0 1px hsla(183, 35%, 55%, 0.3),
+ 0 1px 2px -0.5px var(--shade-active-dark),
+ 0 2px 4px -1px var(--shade-active-dark),
+ 0 4px 8px -2px var(--shade-active-dark);
+
+ /* Warning/error active state — flamingo pink glow */
+ --shade-active-warn-dark: rgba(253, 117, 127, 0.45);
+
+ --perfect-shadow-warn-dark: 0 0 0 1px rgba(253, 117, 127, 0.3),
+ 0 1px 1px -0.5px var(--shade-active-warn-dark),
+ 0 3px 3px -1.5px var(--shade-active-warn-dark),
+ 0 6px 6px -3px var(--shade-active-warn-dark),
+ 0 12px 12px -6px var(--shade-active-warn-dark);
+
+ --mountain-white-dark: rgb(245, 242, 242); /* ≈ 80% – popular "dark mode off-white" */
+ --mountain-white: rgb(255, 252, 252, 1);
+ --ghost-white: rgb(248, 248, 255, 1);
+
+ /* Very subtle darkening – still very bright */
+ --mountain-white-darker-1: rgb(245, 242, 242); /* ≈ 96% brightness */
+ --mountain-white-darker-2: rgb(235, 232, 232); /* ≈ 92% */
+
+ /* Noticeably darker but still clearly a very light / "off-white" */
+ --mountain-white-darker-3: rgb(230, 227, 227); /* ≈ 90% */
+ --mountain-white-darker-4: rgb(220, 217, 217); /* ≈ 86% */
+ --mountain-white-darker-5: rgb(204, 201, 201); /* ≈ 80% – popular "dark mode off-white" */
+
+ /* Deeper / moodier off-whites – start feeling more greyish */
+ --mountain-white-darker-6: rgb(189, 186, 186); /* ≈ 74% */
+ --mountain-white-darker-7: rgb(170, 167, 167); /* ≈ 67% – quite smoky now */
+ --mountain-white-darker-8: rgb(150, 147, 147); /* ≈ 59% – very muted/dark off-white */
+
+ --charcoal-deep: rgb(15, 20, 25);
+ --charcoal-dark: rgb(25, 30, 35); /* +10 */
+ --charcoal-medium: rgb(35, 40, 48); /* +20ish */
+ --charcoal-soft: rgb(45, 50, 58); /* noticeably lighter but still moody */
+ --charcoal-slate: rgb(55, 60, 68); /* good background / surface color */
+ --charcoal-lighter: rgb(70, 75, 85); /* readable dark gray, modern UI feel */
+ --deep-night-black: rgb(8, 18, 28);
+ --abyss-black: rgb(6, 15, 22);
+
+ --bs-dark: var(--charcoal-medium); /* changes the base dark color */
+ --bs-dark-rgb: 35, 40, 48;
+ --bs-blue: var(--glacier-blue);
+
+ /* custom colors */
+ --flamingo-pink: rgba(253, 117, 127, 1);
+ --sea-grass-green: rgba(88, 191, 177, 1);
+
+ --mountain-darkgray: rgb(12, 31, 49, 1);
+ --mountain-gray: rgb(102, 133, 139, 1);
+ --mountain-lightgray: rgb(121, 152, 155, 1);
+
+ --glacier-blue: rgb(28, 80, 101);
+ --glacier-darkblue: rgb(12, 39, 49);
+ --glacier-lightblue: rgb(94, 150, 159);
+ --glacier-blue-rgb: 94, 150, 159;
+
+ /* borders, corners, and shadows */
+ --perfect-border-radius: 0.48em;
+ --backdrop-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 1px 3px 0 rgba(0, 0, 0, 0.19);
+ --backdrop-transition: all 0.3s ease;
+
+ /* global overwrites of bootstrap vars */
+ --text-bg-primary-badge-color: var(--glacier-blue);
+
+ /* White to Darker Blue diagonal gradient */
+ --gradient-white-dark-blue: linear-gradient(
+ 142deg,
+ var(--ghost-white) 0%,
+ var(--ghost-white) 55%,
+ var(--glacier-lightblue) 67%,
+ var(--glacier-lightblue) 75%,
+ var(--glacier-blue) 100%
+ );
+ --gradient-dark: linear-gradient(135deg, var(--mountain-white) 0%, var(--mountain-darkgray) 100%);
+}
+
+* {
+ scrollbar-width: thin;
+ scrollbar-color: color-mix(in oklch, var(--color-base-content) 28%, transparent) var(--color-base-200);
+}
+
+*::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+*::-webkit-scrollbar-track {
+ background: var(--color-base-200);
+}
+
+*::-webkit-scrollbar-thumb {
+ background-color: color-mix(in oklch, var(--color-base-content) 28%, transparent);
+ border-radius: var(--radius-field);
+ border: 2px solid var(--color-base-200);
+}
+
+*::-webkit-scrollbar-thumb:hover {
+ background-color: color-mix(in oklch, var(--color-primary) 65%, transparent);
+}
+
+*::-webkit-scrollbar-corner {
+ background: var(--color-base-200);
+}
+
+html, body {
+ height: 100%;
+ width: 100%;
+ margin: 0;
+ overflow: hidden;
+ background-color: var(--color-base-200);
+ color: var(--color-base-content);
+}
+
+body > * {
+ height: 100%;
+ max-height: 100%;
+ min-height: 0;
+}
+
+.drawer,
+.drawer-content,
+.drawer-side {
+ height: 100%;
+ max-height: 100%;
+ min-height: 0 !important;
+}
+
+.drawer-content {
+ overflow: hidden;
+}
+
+html, :root, body {
+ font-size: 14px;
+}
+
+html, body {
+ font-family: "Geist Variable", "Geist", system-ui, sans-serif;
+ font-weight: 300;
+ font-synthesis: none; /* no fake 700 from 400 */
+}
+
+h1, h2, h3 {
+ font-weight: 300;
+}
+
+h4, h5, h6, p {
+ font-weight: 400;
+}
+
+.btn {
+ box-shadow: var(--perfect-shadow-thin-dark);
+ transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
+}
+
+.btn:hover {
+ transform: scale(1.1) translateY(-2px);
+ box-shadow: var(--perfect-shadow-thin-active-dark);
+}
+
+.btn:not(.btn-ghost):not(.btn-outline):hover {
+ transform: scale(1.1) translateY(-2px);
+ box-shadow: var(--perfect-shadow-thin-active-dark);
+}
+
+.btn,
+.menu a,
+.tab,
+.table :where(th) {
+ font-weight: 400;
+}
\ No newline at end of file