diff --git a/crates/cdk-ldk-node/Cargo.toml b/crates/cdk-ldk-node/Cargo.toml index 94e3ca0b..e252aaa3 100644 --- a/crates/cdk-ldk-node/Cargo.toml +++ b/crates/cdk-ldk-node/Cargo.toml @@ -15,7 +15,7 @@ async-trait.workspace = true axum.workspace = true cdk-common = { workspace = true, features = ["mint"] } futures.workspace = true -tokio.workspace = true +tokio.workspace = true tokio-util.workspace = true tracing.workspace = true thiserror.workspace = true @@ -29,6 +29,3 @@ tower-http.workspace = true rust-embed = "8.5.0" serde_urlencoded = "0.7" urlencoding = "2.1" - - - diff --git a/crates/cdk-ldk-node/src/web/handlers/channels.rs b/crates/cdk-ldk-node/src/web/handlers/channels.rs index 238f9365..dcbe010f 100644 --- a/crates/cdk-ldk-node/src/web/handlers/channels.rs +++ b/crates/cdk-ldk-node/src/web/handlers/channels.rs @@ -213,7 +213,7 @@ pub async fn post_open_channel( } pub async fn close_channel_page( - State(_state): State, + State(state): State, query: Query>, ) -> Result, StatusCode> { let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone(); @@ -229,24 +229,40 @@ pub async fn close_channel_page( return Ok(Html(layout("Close Channel Error", content).into_string())); } + // Get channel information for amount display + let channels = state.node.inner.list_channels(); + let channel = channels + .iter() + .find(|c| c.user_channel_id.0.to_string() == channel_id); + let content = form_card( "Close Channel", html! { - p { "Are you sure you want to close this channel?" } - div class="info-item" { - span class="info-label" { "User Channel ID:" } - span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) } + p style="margin-bottom: 1.5rem;" { "Are you sure you want to close this channel?" } + + // Channel details in consistent format + div class="channel-details" { + div class="detail-row" { + span class="detail-label" { "User Channel ID" } + span class="detail-value-amount" { (channel_id) } + } + div class="detail-row" { + span class="detail-label" { "Node ID" } + span class="detail-value-amount" { (node_id) } + } + @if let Some(ch) = channel { + div class="detail-row" { + span class="detail-label" { "Channel Amount" } + span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) } + } + } } - div class="info-item" { - span class="info-label" { "Node ID:" } - span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) } - } - form method="post" action="/channels/close" style="margin-top: 1rem;" { + + form method="post" action="/channels/close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" { input type="hidden" name="channel_id" value=(channel_id) {} input type="hidden" name="node_id" value=(node_id) {} - button type="submit" style="background: #dc3545;" { "Close Channel" } - " " - a href="/balance" { button type="button" { "Cancel" } } + a href="/balance" { button type="button" class="button-secondary" { "Cancel" } } + button type="submit" class="button-destructive" { "Close Channel" } } }, ); @@ -255,7 +271,7 @@ pub async fn close_channel_page( } pub async fn force_close_channel_page( - State(_state): State, + State(state): State, query: Query>, ) -> Result, StatusCode> { let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone(); @@ -273,32 +289,48 @@ pub async fn force_close_channel_page( )); } + // Get channel information for amount display + let channels = state.node.inner.list_channels(); + let channel = channels + .iter() + .find(|c| c.user_channel_id.0.to_string() == channel_id); + let content = form_card( "Force Close Channel", html! { - div style="border: 2px solid #d63384; background-color: rgba(214, 51, 132, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" { - h4 style="color: #d63384; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" } - p style="color: #d63384; margin: 0; font-size: 0.9rem;" { + div style="border: 2px solid #f97316; background-color: rgba(249, 115, 22, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" { + h4 style="color: #f97316; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" } + p style="color: #f97316; margin: 0; font-size: 0.9rem;" { "Force close should NOT be used if normal close is preferred. " "Force close will immediately broadcast the latest commitment transaction and may result in delayed fund recovery. " "Only use this if the channel counterparty is unresponsive or there are other issues preventing normal closure." } } - p { "Are you sure you want to force close this channel?" } - div class="info-item" { - span class="info-label" { "User Channel ID:" } - span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) } + p style="margin-bottom: 1.5rem;" { "Are you sure you want to force close this channel?" } + + // Channel details in consistent format + div class="channel-details" { + div class="detail-row" { + span class="detail-label" { "User Channel ID" } + span class="detail-value-amount" { (channel_id) } + } + div class="detail-row" { + span class="detail-label" { "Node ID" } + span class="detail-value-amount" { (node_id) } + } + @if let Some(ch) = channel { + div class="detail-row" { + span class="detail-label" { "Channel Amount" } + span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) } + } + } } - div class="info-item" { - span class="info-label" { "Node ID:" } - span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) } - } - form method="post" action="/channels/force-close" style="margin-top: 1rem;" { + + form method="post" action="/channels/force-close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" { input type="hidden" name="channel_id" value=(channel_id) {} input type="hidden" name="node_id" value=(node_id) {} - button type="submit" style="background: #d63384;" { "Force Close Channel" } - " " - a href="/balance" { button type="button" { "Cancel" } } + a href="/balance" { button type="button" class="button-secondary" { "Cancel" } } + button type="submit" class="button-destructive" { "Force Close Channel" } } }, ); diff --git a/crates/cdk-ldk-node/src/web/handlers/lightning.rs b/crates/cdk-ldk-node/src/web/handlers/lightning.rs index 344b78b3..8ca3b544 100644 --- a/crates/cdk-ldk-node/src/web/handlers/lightning.rs +++ b/crates/cdk-ldk-node/src/web/handlers/lightning.rs @@ -3,7 +3,7 @@ use axum::http::StatusCode; use axum::response::Html; use maud::html; -use crate::web::handlers::AppState; +use crate::web::handlers::utils::AppState; use crate::web::templates::{format_sats_as_btc, layout}; pub async fn balance_page(State(state): State) -> Result, StatusCode> { @@ -26,18 +26,35 @@ pub async fn balance_page(State(state): State) -> Result, html! { h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" } - // Quick Actions section - matching dashboard style + // Quick Actions section - individual cards div class="card" style="margin-bottom: 2rem;" { h2 { "Quick Actions" } - div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" { - a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Open Channel" } + div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" { + // Open Channel Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning Network channel to connect with another node." } + a href="/channels/open" style="text-decoration: none;" { + button class="button-outline" { "Open Channel" } + } } - a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Create Invoice" } + + // Create Invoice Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments from other users or services." } + a href="/invoices" style="text-decoration: none;" { + button class="button-outline" { "Create Invoice" } + } } - a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Make Lightning Payment" } + + // Make Payment Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices. BOLT 11 & 12 supported." } + a href="/invoices" style="text-decoration: none;" { + button class="button-outline" { "Make Payment" } + } } } } @@ -73,18 +90,35 @@ pub async fn balance_page(State(state): State) -> Result, html! { h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" } - // Quick Actions section - matching dashboard style + // Quick Actions section - individual cards div class="card" style="margin-bottom: 2rem;" { h2 { "Quick Actions" } - div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" { - a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Open Channel" } + div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" { + // Open Channel Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning channel by connecting with another node." } + a href="/channels/open" style="text-decoration: none;" { + button class="button-outline" { "Open Channel" } + } } - a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Create Invoice" } + + // Create Invoice Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments." } + a href="/invoices" style="text-decoration: none;" { + button class="button-outline" { "Create Invoice" } + } } - a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Make Lightning Payment" } + + // Make Payment Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices." } + a href="/payments/send" style="text-decoration: none;" { + button class="button-outline" { "Make Payment" } + } } } } @@ -112,57 +146,72 @@ pub async fn balance_page(State(state): State) -> Result, } } - div class="card" { - h2 { "Channel Details" } + // Channel Details header (outside card) + h2 class="section-header" { "Channel Details" } - // Channels list - @for channel in &channels { - div class="channel-item" { - div class="channel-header" { - span class="channel-id" { "Channel ID: " (channel.channel_id.to_string()) } + // Channels list + @for (index, channel) in channels.iter().enumerate() { + @let node_id = channel.counterparty_node_id.to_string(); + @let channel_number = index + 1; + + div class="channel-box" { + // Channel number as prominent header + div class="channel-alias" { (format!("Channel {}", channel_number)) } + + // Channel details in left-aligned format + div class="channel-details" { + div class="detail-row" { + span class="detail-label" { "Channel ID" } + span class="detail-value-amount" { (channel.channel_id.to_string()) } + } + @if let Some(short_channel_id) = channel.short_channel_id { + div class="detail-row" { + span class="detail-label" { "Short Channel ID" } + span class="detail-value-amount" { (short_channel_id.to_string()) } + } + } + div class="detail-row" { + span class="detail-label" { "Node ID" } + span class="detail-value-amount" { (node_id) } + } + div class="detail-row" { + span class="detail-label" { "Status" } @if channel.is_usable { span class="status-badge status-active" { "Active" } } @else { span class="status-badge status-inactive" { "Inactive" } } } - div class="info-item" { - span class="info-label" { "Counterparty" } - span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel.counterparty_node_id.to_string()) } + } + + // Balance information cards (keeping existing style) + div class="balance-info" { + div class="balance-item" { + div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) } + div class="balance-label" { "Outbound" } } - @if let Some(short_channel_id) = channel.short_channel_id { - div class="info-item" { - span class="info-label" { "Short Channel ID" } - span class="info-value" { (short_channel_id.to_string()) } - } + div class="balance-item" { + div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) } + div class="balance-label" { "Inbound" } } - div class="balance-info" { - div class="balance-item" { - div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) } - div class="balance-label" { "Outbound" } - } - div class="balance-item" { - div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) } - div class="balance-label" { "Inbound" } - } - div class="balance-item" { - div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) } - div class="balance-label" { "Total" } - } + div class="balance-item" { + div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) } + div class="balance-label" { "Total" } } - @if channel.is_usable { - div style="margin-top: 1rem; display: flex; gap: 0.5rem;" { - a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) { - button style="background: #dc3545;" { "Close Channel" } - } - a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) { - button style="background: #d63384;" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" } - } + } + + // Action buttons + @if channel.is_usable { + div class="channel-actions" { + a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) { + button class="button-secondary" { "Close Channel" } + } + a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) { + button class="button-destructive" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" } } } } } - } } }; diff --git a/crates/cdk-ldk-node/src/web/handlers/onchain.rs b/crates/cdk-ldk-node/src/web/handlers/onchain.rs index ee34076f..1658d0e0 100644 --- a/crates/cdk-ldk-node/src/web/handlers/onchain.rs +++ b/crates/cdk-ldk-node/src/web/handlers/onchain.rs @@ -79,15 +79,26 @@ pub async fn onchain_page( let mut content = html! { h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" } - // Quick Actions section - matching dashboard style + // Quick Actions section - individual cards div class="card" style="margin-bottom: 2rem;" { h2 { "Quick Actions" } - div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" { - a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Receive Bitcoin" } + div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" { + // Receive Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." } + a href="/onchain?action=receive" style="text-decoration: none;" { + button class="button-outline" { "Receive Bitcoin" } + } } - a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Send Bitcoin" } + + // Send Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." } + a href="/onchain?action=send" style="text-decoration: none;" { + button class="button-outline" { "Send Bitcoin" } + } } } } @@ -113,15 +124,26 @@ pub async fn onchain_page( content = html! { h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" } - // Quick Actions section - matching dashboard style + // Quick Actions section - individual cards div class="card" style="margin-bottom: 2rem;" { h2 { "Quick Actions" } - div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" { - a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Receive Bitcoin" } + div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" { + // Receive Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." } + a href="/onchain?action=receive" style="text-decoration: none;" { + button class="button-outline" { "Receive Bitcoin" } + } } - a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Send Bitcoin" } + + // Send Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." } + a href="/onchain?action=send" style="text-decoration: none;" { + button class="button-outline" { "Send Bitcoin" } + } } } } @@ -141,7 +163,7 @@ pub async fn onchain_page( } input type="hidden" id="send_action" name="send_action" value="send" {} div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" { - a href="/onchain" { button type="button" { "Cancel" } } + a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } } div style="display: flex; gap: 0.5rem;" { button type="submit" onclick="document.getElementById('send_action').value='send'" { "Send Payment" } button type="submit" onclick="document.getElementById('send_action').value='send_all'; document.getElementById('amount_sat').value=''" { "Send All" } @@ -171,15 +193,26 @@ pub async fn onchain_page( content = html! { h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" } - // Quick Actions section - matching dashboard style + // Quick Actions section - individual cards div class="card" style="margin-bottom: 2rem;" { h2 { "Quick Actions" } - div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" { - a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Receive Bitcoin" } + div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" { + // Receive Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." } + a href="/onchain?action=receive" style="text-decoration: none;" { + button class="button-outline" { "Receive Bitcoin" } + } } - a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" { - button class="button-primary" style="width: 100%;" { "Send Bitcoin" } + + // Send Bitcoin Card + div class="quick-action-card" { + h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" } + p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." } + a href="/onchain?action=send" style="text-decoration: none;" { + button class="button-outline" { "Send Bitcoin" } + } } } } @@ -191,7 +224,7 @@ pub async fn onchain_page( form method="post" action="/onchain/new-address" { p style="margin-bottom: 2rem;" { "Click the button below to generate a new Bitcoin address for receiving on-chain payments." } div style="display: flex; justify-content: space-between; gap: 1rem;" { - a href="/onchain" { button type="button" { "Cancel" } } + a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } } button class="button-primary" type="submit" { "Generate New Address" } } } @@ -345,7 +378,7 @@ pub async fn onchain_confirm_page( div class="card" { div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" { a href="/onchain?action=send" { - button type="button" class="button-secondary" { "← Cancel" } + button type="button" class="button-secondary" { "Cancel" } } div style="display: flex; gap: 0.5rem;" { a href=(confirmation_url) { diff --git a/crates/cdk-ldk-node/src/web/templates/layout.rs b/crates/cdk-ldk-node/src/web/templates/layout.rs index d055390d..4fd25ef8 100644 --- a/crates/cdk-ldk-node/src/web/templates/layout.rs +++ b/crates/cdk-ldk-node/src/web/templates/layout.rs @@ -34,74 +34,299 @@ pub fn layout(title: &str, content: Markup) -> Markup { --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; --radius: 0.5rem; - + /* Typography scale */ --fs-title: 1.25rem; --fs-label: 0.8125rem; --fs-value: 1.625rem; - + /* Line heights */ --lh-tight: 1.15; --lh-normal: 1.4; - + /* Font weights */ --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700; - + /* Colors */ --fg-primary: #0f172a; --fg-muted: #6b7280; - + /* Header text colors for light mode */ --header-title: #000000; --header-subtitle: #333333; } - /* Dark mode using system preference */ + /* Dark mode using system preference */ @media (prefers-color-scheme: dark) { + body { + background: linear-gradient(rgb(23, 25, 29), rgb(18, 19, 21)); + } + :root { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 84% 4.9%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; + --background: 0 0% 0%; + --foreground: 0 0% 100%; + --card: 0 0% 0%; + --card-foreground: 0 0% 100%; + --popover: 0 0% 0%; + --popover-foreground: 0 0% 100%; + --primary: 0 0% 100%; + --primary-foreground: 0 0% 0%; + --secondary: 0 0% 20%; + --secondary-foreground: 0 0% 100%; + --muted: 0 0% 20%; + --muted-foreground: 0 0% 70%; + --accent: 0 0% 20%; + --accent-foreground: 0 0% 100%; --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - - /* Dark mode colors */ - --fg-primary: #f8fafc; - --fg-muted: #94a3b8; - + --destructive-foreground: 0 0% 100%; + --border: 0 0% 20%; + --input: 0 0% 20%; + --ring: 0 0% 83.9%; + + /* Dark mode text hierarchy colors */ + --text-primary: #ffffff; + --text-secondary: #e6e6e6; + --text-tertiary: #cccccc; + --text-quaternary: #b3b3b3; + --text-muted: #999999; + --text-muted-2: #888888; + --text-muted-3: #666666; + --text-muted-4: #333333; + --text-subtle: #1a1a1a; + /* Header text colors for dark mode */ --header-title: #ffffff; - --header-subtitle: #e2e8f0; + --header-subtitle: #e6e6e6; + } + + /* Dark mode box styling - no borders, subtle background */ + .card { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + .channel-box { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + .metric-card { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + .balance-item { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + .node-info-main-container { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + .node-avatar { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + } + + /* Text hierarchy colors */ + .section-header { + color: var(--text-primary) !important; + } + + .channel-alias { + color: var(--text-primary) !important; + } + + .detail-label { + color: var(--text-muted) !important; + } + + .detail-value, .detail-value-amount { + color: var(--text-secondary) !important; + } + + .metric-label, .balance-label { + color: var(--text-muted) !important; + } + + .metric-value, .balance-amount { + color: var(--text-primary) !important; + } + + /* Page headers and section titles */ + h1, h2, h3, h4, h5, h6 { + color: var(--text-primary) !important; + } + + /* Form card titles */ + .form-card h2, .form-card h3 { + color: var(--text-primary) !important; + } + + /* Quick action cards styling */ + .quick-action-card { + background-color: rgba(255, 255, 255, 0.03) !important; + border: none !important; + border-radius: 0.75rem !important; + padding: 1.5rem !important; + } + + /* Dark mode outline button styling */ + .button-outline { + background-color: transparent !important; + color: var(--text-primary) !important; + border: 1px solid var(--text-muted) !important; + } + + .button-outline:hover { + background-color: rgba(255, 255, 255, 0.2) !important; + } + + /* Navigation dark mode styling */ + nav { + background-color: transparent !important; + border-top: none !important; + border-bottom: none !important; + } + + } + + /* New Header Layout Styles */ + .header-content { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + } + + .header-left { + display: flex; + align-items: center; + gap: 1rem; + } + + .header-avatar { + flex-shrink: 0; + background-color: hsl(var(--muted) / 0.3); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + padding: 0.75rem; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + height: 80px; + } + + .header-avatar-image { + width: 48px; + height: 48px; + border-radius: calc(var(--radius) - 2px); + object-fit: cover; + display: block; + } + + .node-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding-top: 0; + margin-top: 0; + } + + .node-status { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .status-indicator { + width: 0.75rem; + height: 0.75rem; + border-radius: 50%; + background-color: #10b981; + box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); + } + + .status-text { + font-size: 0.875rem; + font-weight: 500; + color: #10b981; + } + + .node-title { + font-size: 1.875rem; + font-weight: 600; + color: var(--header-title); + margin: 0; + line-height: 1.1; + } + + .node-subtitle { + font-size: 0.875rem; + color: var(--header-subtitle); + font-weight: 500; + } + + .header-right { + display: flex; + align-items: center; + } + + + + /* Responsive header */ + @media (max-width: 768px) { + .header-content { + flex-direction: column; + gap: 1rem; + text-align: center; + } + + .header-left { + flex-direction: column; + text-align: center; + } + + .node-title { + font-size: 1.5rem; } } - + + nav a { + color: var(--text-muted) !important; + } + + nav a:hover { + color: var(--text-secondary) !important; + background-color: rgba(255, 255, 255, 0.05) !important; + } + + nav a.active { + color: var(--text-primary) !important; + background-color: rgba(255, 255, 255, 0.08) !important; + } + + nav a.active:hover { + background-color: rgba(255, 255, 255, 0.1) !important; + } + } + * { box-sizing: border-box; margin: 0; padding: 0; } - + html { font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; font-variation-settings: normal; } - + body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 14px; @@ -114,19 +339,19 @@ pub fn layout(title: &str, content: Markup) -> Markup { text-rendering: geometricPrecision; min-height: 100vh; } - + .container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; } - + @media (min-width: 640px) { .container { padding: 0 2rem; } } - + /* Hero section styling */ header { position: relative; @@ -135,34 +360,37 @@ pub fn layout(title: &str, content: Markup) -> Markup { background-position: center; background-repeat: no-repeat; border-bottom: 1px solid hsl(var(--border)); - margin-bottom: 3rem; - text-align: center; + margin-bottom: 2rem; + text-align: left; width: 100%; - height: 400px; /* Fixed height for better proportion */ + height: 200px; /* Reduced height for more compact header */ display: flex; align-items: center; - justify-content: center; + justify-content: flex-start; } - + /* Dark mode header background - using different image */ @media (prefers-color-scheme: dark) { header { background-image: url('/static/images/bg-dark.jpg?v=3'); } } - + /* Ensure text is positioned properly */ header .container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: relative; + top: auto; + left: auto; + transform: none; z-index: 2; width: 100%; max-width: 1200px; padding: 0 2rem; + display: flex; + align-items: center; + justify-content: flex-start; } - + h1 { font-size: 3rem; font-weight: 700; @@ -171,7 +399,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: var(--header-title); margin-bottom: 1rem; } - + .subtitle { font-size: 1.25rem; color: var(--header-subtitle); @@ -180,35 +408,35 @@ pub fn layout(title: &str, content: Markup) -> Markup { margin: 0 auto; line-height: 1.6; } - + @media (max-width: 768px) { header { - height: 300px; /* Smaller height on mobile */ + height: 150px; /* Smaller height on mobile */ } - + header .container { padding: 0 1rem; } - + h1 { font-size: 2.25rem; } - + .subtitle { font-size: 1.1rem; } } - + /* Card fade-in animation */ @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } - + .card { animation: fade-in 0.3s ease-out; } - + /* Modern Navigation Bar Styling */ nav { background-color: hsl(var(--card)); @@ -220,13 +448,13 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 0.75rem; margin-bottom: 2rem; } - + nav .container { padding: 0; display: flex; justify-content: center; } - + nav ul { list-style: none; display: flex; @@ -237,11 +465,11 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 0; justify-content: center; } - + nav li { flex-shrink: 0; } - + nav a { display: inline-flex; align-items: center; @@ -257,22 +485,22 @@ pub fn layout(title: &str, content: Markup) -> Markup { position: relative; min-height: 3rem; } - + nav a:hover { color: hsl(var(--foreground)); background-color: hsl(var(--muted)); } - + nav a.active { color: hsl(var(--primary-foreground)); background-color: hsl(var(--primary)); font-weight: 700; } - + nav a.active:hover { background-color: hsl(var(--primary) / 0.9); } - + .card { background-color: hsl(var(--card)); border: 1px solid hsl(var(--border)); @@ -281,7 +509,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } - + /* Metric cards styling - matching balance-item style */ .metrics-container { display: flex; @@ -289,7 +517,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { margin: 1rem 0; flex-wrap: wrap; } - + .metric-card { flex: 1; min-width: 200px; @@ -299,7 +527,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { border-radius: calc(var(--radius) - 2px); border: 1px solid hsl(var(--border)); } - + .metric-value { font-size: 1.5rem; font-weight: 600; @@ -307,13 +535,13 @@ pub fn layout(title: &str, content: Markup) -> Markup { margin-bottom: 0.5rem; line-height: 1.2; } - + .metric-label { font-size: 0.875rem; color: hsl(var(--muted-foreground)); font-weight: 400; } - + .card h2, .section-title, h2 { @@ -324,7 +552,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { text-transform: none; margin: 0 0 12px; } - + h3 { font-size: var(--fs-title); line-height: var(--lh-tight); @@ -333,11 +561,11 @@ pub fn layout(title: &str, content: Markup) -> Markup { text-transform: none; margin: 0 0 12px; } - + .form-group { margin-bottom: 1.5rem; } - + label { display: block; font-size: 0.875rem; @@ -345,7 +573,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: hsl(var(--foreground)); margin-bottom: 0.5rem; } - + input, textarea, select { flex: 1; background-color: hsl(var(--background)); @@ -358,19 +586,19 @@ pub fn layout(title: &str, content: Markup) -> Markup { transition: border-color 150ms ease-in-out, box-shadow 150ms ease-in-out; width: 100%; } - + input:focus, textarea:focus, select:focus { outline: 2px solid transparent; outline-offset: 2px; border-color: hsl(var(--ring)); box-shadow: 0 0 0 2px hsl(var(--ring)); } - + input:disabled, textarea:disabled, select:disabled { cursor: not-allowed; opacity: 0.5; } - + button { display: inline-flex; align-items: center; @@ -387,79 +615,82 @@ pub fn layout(title: &str, content: Markup) -> Markup { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } - + button:hover { background-color: hsl(var(--primary) / 0.9); } - + button:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } - + button:disabled { pointer-events: none; opacity: 0.5; } - + .button-secondary { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); border: 1px solid hsl(var(--input)); } - + .button-secondary:hover { background-color: hsl(var(--secondary) / 0.8); } - + .button-outline { border: 1px solid hsl(var(--input)); background-color: hsl(var(--background)); color: hsl(var(--foreground)); } - + .button-outline:hover { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } - + .button-destructive { - background-color: hsl(var(--destructive)); - color: hsl(var(--destructive-foreground)); + background-color: transparent !important; + color: #DC2626 !important; + border: 1px solid #DC2626 !important; } - + .button-destructive:hover { - background-color: hsl(var(--destructive) / 0.9); + background-color: rgba(220, 38, 38, 0.2) !important; } - + + + .button-sm { height: 2rem; border-radius: calc(var(--radius) - 4px); padding: 0 0.75rem; font-size: 0.75rem; } - + .button-lg { height: 2.75rem; border-radius: var(--radius); padding: 0 2rem; font-size: 1rem; } - + .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; } - + @media (max-width: 640px) { .grid { grid-template-columns: 1fr; } } - - + + .info-label, .sub-label, label { @@ -471,7 +702,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { letter-spacing: 0.02em; flex-shrink: 0; } - + .info-value { font-size: 0.875rem; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; @@ -482,7 +713,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { hyphens: auto; min-width: 0; } - + .info-item { display: flex; gap: 0.5rem; @@ -493,43 +724,43 @@ pub fn layout(title: &str, content: Markup) -> Markup { min-height: 3rem; justify-content: space-between; } - + .info-item:last-child { border-bottom: none; } - + /* Card flex spacing improvements */ .card-flex { display: flex; gap: 1rem; align-items: center; } - + .card-flex-content { flex: 1 1 auto; } - + .card-flex-button { flex: 0 0 auto; } - + .card-flex-content p { margin: 0 0 12px; line-height: var(--lh-normal); } - + .card-flex-content p + .card-flex-button, .card-flex-content p + a, .card-flex-content p + button { margin-top: 12px; } - + .card-flex-content .body + .card-flex-button, .card-flex-content .body + a, .card-flex-content .body + button { margin-top: 12px; } - + .truncate-value { font-size: 0.875rem; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; @@ -541,7 +772,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { display: inline-block; max-width: 200px; } - + .copy-button { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); @@ -557,24 +788,24 @@ pub fn layout(title: &str, content: Markup) -> Markup { min-height: auto; flex-shrink: 0; } - + .copy-button:hover { background-color: hsl(var(--secondary) / 0.8); border-color: hsl(var(--border)); } - + .balance-item, .balance-item-container { padding: 1.25rem 0; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 10px; } - + .balance-item:last-child, .balance-item-container:last-child { border-bottom: none; } - + .balance-item .balance-label, .balance-item-container .balance-label, .balance-title, @@ -588,7 +819,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { letter-spacing: 0.02em; text-transform: none; } - + .balance-item .balance-amount, .balance-item-container .balance-value, .balance-amount, @@ -602,39 +833,39 @@ pub fn layout(title: &str, content: Markup) -> Markup { white-space: nowrap; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; } - + .balance-item .info-label + .info-value, .balance-item .label + .amount, .balance-item-container .info-label + .info-value, .balance-item-container .label + .amount { margin-top: 6px; } - + .alert { border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; margin-bottom: 1rem; } - + .alert-success { border-color: hsl(142.1 76.2% 36.3%); background-color: hsl(142.1 70.6% 45.3% / 0.1); color: hsl(142.1 76.2% 36.3%); } - + .alert-destructive { border-color: hsl(var(--destructive)); background-color: hsl(var(--destructive) / 0.1); color: hsl(var(--destructive)); } - + .alert-warning { border-color: hsl(32.6 75.4% 55.1%); background-color: hsl(32.6 75.4% 55.1% / 0.1); color: hsl(32.6 75.4% 55.1%); } - + /* Legacy classes for backward compatibility */ .success { border-color: hsl(142.1 76.2% 36.3%); @@ -645,7 +876,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 1rem; margin-bottom: 1rem; } - + .error { border-color: hsl(var(--destructive)); background-color: hsl(var(--destructive) / 0.1); @@ -655,7 +886,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 1rem; margin-bottom: 1rem; } - + .badge { display: inline-flex; align-items: center; @@ -667,34 +898,34 @@ pub fn layout(title: &str, content: Markup) -> Markup { transition: all 150ms ease-in-out; border: 1px solid transparent; } - + .badge-default { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } - + .badge-secondary { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); } - + .badge-success { background-color: hsl(142.1 70.6% 45.3%); color: hsl(355.7 78% 98.4%); } - + .badge-destructive { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } - + .badge-outline { background-color: transparent; color: hsl(var(--foreground)); border: 1px solid hsl(var(--border)); } - - /* Legacy status classes */ + + /* Status badge classes - consistent with payment type badges */ .status-badge { display: inline-flex; align-items: center; @@ -704,55 +935,127 @@ pub fn layout(title: &str, content: Markup) -> Markup { font-weight: 500; line-height: 1; } - + .status-active { - background-color: hsl(142.1 70.6% 45.3%); - color: hsl(355.7 78% 98.4%); + background-color: hsl(142.1 70.6% 45.3% / 0.1); + color: hsl(142.1 70.6% 45.3%); + border: 1px solid hsl(142.1 70.6% 45.3% / 0.2); } - + .status-inactive { - background-color: hsl(var(--destructive)); - color: hsl(var(--destructive-foreground)); + background-color: hsl(0 84.2% 60.2% / 0.1); + color: hsl(0 84.2% 60.2%); + border: 1px solid hsl(0 84.2% 60.2% / 0.2); } - - .channel-item { + + .status-pending { + background-color: hsl(215.4 16.3% 46.9% / 0.1); + color: hsl(215.4 16.3% 46.9%); + border: 1px solid hsl(215.4 16.3% 46.9% / 0.2); + } + + .channel-box { background-color: hsl(var(--card)); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1.5rem; margin-bottom: 1.5rem; } - - .channel-header { - display: flex; - justify-content: space-between; - align-items: center; + + .section-header { + font-size: 1.25rem; + font-weight: 700; + color: hsl(var(--foreground)); + margin-bottom: 1.5rem; + line-height: 1.2; + } + + .channel-alias { + font-size: 1.25rem; + font-weight: 600; + color: hsl(var(--foreground)); margin-bottom: 1rem; + line-height: 1.2; + } + + .channel-details { + margin-bottom: 1.5rem; + } + + .detail-row { + display: flex; + align-items: baseline; + margin-bottom: 0.75rem; gap: 1rem; } - - .channel-id { + + .detail-row:last-child { + margin-bottom: 0; + } + + .detail-label { + font-weight: 500; + color: hsl(var(--muted-foreground)); + font-size: 0.875rem; + min-width: 120px; + flex-shrink: 0; + } + + .detail-value { + color: hsl(var(--foreground)); font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; font-size: 0.875rem; - color: hsl(var(--muted-foreground)); word-break: break-all; flex: 1; min-width: 0; } - + + .detail-value-amount { + color: hsl(var(--foreground)); + font-size: 0.875rem; + word-break: break-all; + flex: 1; + min-width: 0; + } + + .channel-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1rem; + gap: 1rem; + } + + @media (max-width: 640px) { + .channel-actions { + flex-direction: column; + align-items: stretch; + } + + .detail-row { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } + + .detail-label { + min-width: auto; + } + } + .balance-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-top: 1rem; } - + @media (max-width: 640px) { .balance-info { grid-template-columns: 1fr; } } - + .balance-item { text-align: center; padding: 1rem; @@ -760,7 +1063,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { border-radius: calc(var(--radius) - 2px); border: 1px solid hsl(var(--border)); } - + .balance-amount { font-weight: 600; font-size: 1.125rem; @@ -768,9 +1071,9 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: hsl(var(--foreground)); line-height: 1.2; } - - + + .payment-item { background-color: hsl(var(--card)); border: 1px solid hsl(var(--border)); @@ -778,7 +1081,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 1.5rem; margin-bottom: 1.5rem; } - + .payment-header { display: flex; justify-content: space-between; @@ -786,7 +1089,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { margin-bottom: 1rem; gap: 1rem; } - + @media (max-width: 640px) { .payment-header { flex-direction: column; @@ -794,7 +1097,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { gap: 0.75rem; } } - + .payment-direction { display: flex; align-items: center; @@ -804,19 +1107,19 @@ pub fn layout(title: &str, content: Markup) -> Markup { flex: 1; min-width: 0; } - + .direction-icon { font-size: 1.125rem; font-weight: bold; color: hsl(var(--muted-foreground)); } - + .payment-details { display: flex; flex-direction: column; gap: 0.75rem; } - + .payment-amount { font-size: 1.25rem; font-weight: 600; @@ -824,14 +1127,14 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: hsl(var(--foreground)); line-height: 1.2; } - + .payment-info { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; } - + @media (max-width: 640px) { .payment-info { flex-direction: column; @@ -839,14 +1142,14 @@ pub fn layout(title: &str, content: Markup) -> Markup { gap: 0.25rem; } } - + .payment-label { font-weight: 500; color: hsl(var(--muted-foreground)); font-size: 0.875rem; flex-shrink: 0; } - + .payment-value { color: hsl(var(--foreground)); font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; @@ -854,7 +1157,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { word-break: break-all; min-width: 0; } - + .payment-list-header { display: flex; justify-content: space-between; @@ -863,7 +1166,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); } - + @media (max-width: 640px) { .payment-list-header { flex-direction: column; @@ -871,14 +1174,14 @@ pub fn layout(title: &str, content: Markup) -> Markup { gap: 1rem; } } - + .payment-filter-tabs { display: flex; gap: 0.25rem; overflow-x: auto; -webkit-overflow-scrolling: touch; } - + .payment-filter-tab { display: inline-flex; align-items: center; @@ -895,19 +1198,43 @@ pub fn layout(title: &str, content: Markup) -> Markup { transition: all 150ms ease-in-out; height: 2.25rem; } - + .payment-filter-tab:hover { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); text-decoration: none; } - + .payment-filter-tab.active { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); } - + + /* Dark mode specific styling for payment filter tabs */ + @media (prefers-color-scheme: dark) { + .payment-filter-tab { + background-color: rgba(255, 255, 255, 0.03) !important; + border-color: var(--text-muted) !important; + color: var(--text-muted) !important; + } + + .payment-filter-tab:hover { + background-color: rgba(255, 255, 255, 0.08) !important; + color: var(--text-secondary) !important; + } + + .payment-filter-tab.active { + background-color: rgba(255, 255, 255, 0.12) !important; + color: var(--text-primary) !important; + border-color: var(--text-secondary) !important; + } + + .payment-filter-tab.active:hover { + background-color: rgba(255, 255, 255, 0.15) !important; + } + } + .payment-type-badge { display: inline-flex; align-items: center; @@ -920,43 +1247,43 @@ pub fn layout(title: &str, content: Markup) -> Markup { text-transform: uppercase; letter-spacing: 0.05em; } - + .payment-type-bolt11 { background-color: hsl(217 91% 60% / 0.1); color: hsl(217 91% 60%); border: 1px solid hsl(217 91% 60% / 0.2); } - + .payment-type-bolt12 { background-color: hsl(262 83% 58% / 0.1); color: hsl(262 83% 58%); border: 1px solid hsl(262 83% 58% / 0.2); } - + .payment-type-onchain { background-color: hsl(32 95% 44% / 0.1); color: hsl(32 95% 44%); border: 1px solid hsl(32 95% 44% / 0.2); } - + .payment-type-spontaneous { background-color: hsl(142.1 70.6% 45.3% / 0.1); color: hsl(142.1 70.6% 45.3%); border: 1px solid hsl(142.1 70.6% 45.3% / 0.2); } - + .payment-type-bolt11-jit { background-color: hsl(199 89% 48% / 0.1); color: hsl(199 89% 48%); border: 1px solid hsl(199 89% 48% / 0.2); } - + .payment-type-unknown { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); border: 1px solid hsl(var(--border)); } - + /* Pagination */ .pagination-controls { display: flex; @@ -964,14 +1291,14 @@ pub fn layout(title: &str, content: Markup) -> Markup { align-items: center; margin: 2rem 0; } - + .pagination { display: flex; align-items: center; gap: 0.25rem; list-style: none; } - + .pagination-btn, .pagination-number { display: inline-flex; align-items: center; @@ -990,19 +1317,19 @@ pub fn layout(title: &str, content: Markup) -> Markup { min-width: 2.25rem; padding: 0 0.5rem; } - + .pagination-btn:hover, .pagination-number:hover { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); text-decoration: none; } - + .pagination-number.active { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); } - + .pagination-btn.disabled { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); @@ -1010,7 +1337,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { opacity: 0.5; pointer-events: none; } - + .pagination-ellipsis { display: flex; align-items: center; @@ -1020,31 +1347,31 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: hsl(var(--muted-foreground)); font-size: 0.875rem; } - + /* Responsive adjustments */ @media (max-width: 640px) { .container { padding: 0 1rem; } - + header { padding: 1rem 0; margin-bottom: 1rem; } - + h1 { font-size: 1.5rem; } - + nav ul { flex-wrap: wrap; } - + .card { padding: 1rem; margin-bottom: 1rem; } - + .info-item { flex-direction: column; align-items: flex-start; @@ -1052,43 +1379,43 @@ pub fn layout(title: &str, content: Markup) -> Markup { padding: 1rem 0; min-height: auto; } - + .info-value, .truncate-value { text-align: left; max-width: 100%; } - + .copy-button { margin-left: 0; margin-top: 0.25rem; align-self: flex-start; } - + .balance-amount-value { font-size: 1.25rem; } - + .pagination { flex-wrap: wrap; justify-content: center; gap: 0.125rem; } - + .pagination-btn, .pagination-number { height: 2rem; min-width: 2rem; font-size: 0.75rem; } } - + /* Node Information Section Styling */ .node-info-section { display: flex; gap: 1.5rem; margin-bottom: 1.5rem; - align-items: flex-start; + align-items: stretch; } - + .node-info-main-container { flex: 1; display: flex; @@ -1099,15 +1426,16 @@ pub fn layout(title: &str, content: Markup) -> Markup { border-radius: var(--radius); padding: 1.5rem; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + height: 100%; } - + .node-info-left { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; } - + .node-avatar { flex-shrink: 0; background-color: hsl(var(--muted) / 0.3); @@ -1120,7 +1448,7 @@ pub fn layout(title: &str, content: Markup) -> Markup { width: 80px; height: 80px; } - + .avatar-image { width: 48px; height: 48px; @@ -1128,12 +1456,12 @@ pub fn layout(title: &str, content: Markup) -> Markup { object-fit: cover; display: block; } - + .node-details { flex: 1; min-width: 0; } - + .node-name { font-size: var(--fs-title); font-weight: var(--fw-semibold); @@ -1144,14 +1472,14 @@ pub fn layout(title: &str, content: Markup) -> Markup { overflow-wrap: break-word; hyphens: auto; } - + .node-address { font-size: 0.875rem; color: var(--fg-muted); margin: 0; line-height: var(--lh-normal); } - + .node-content-box { background-color: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); @@ -1164,106 +1492,119 @@ pub fn layout(title: &str, content: Markup) -> Markup { color: hsl(var(--muted-foreground)); overflow: hidden; } - + .node-metrics { flex-shrink: 0; width: 280px; display: flex; flex-direction: column; + align-self: stretch; } - + .node-metrics .card { margin-bottom: 0; flex: 1; display: flex; flex-direction: column; + align-self: stretch; } - + .node-metrics .metrics-container { flex-direction: column; margin: 1rem 0 0 0; flex: 1; + display: flex; + justify-content: flex-start; + gap: 1rem; + align-items: stretch; } - + .node-metrics .metric-card { min-width: auto; + padding: 1rem; + height: fit-content; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; } - + /* Mobile responsive design for node info */ @media (max-width: 768px) { .node-info-section { flex-direction: column; gap: 1rem; } - + .node-info-left { flex-direction: column; align-items: flex-start; text-align: center; gap: 0.75rem; } - + .node-avatar { align-self: center; } - + .node-details { text-align: center; width: 100%; } - + .node-content-box { min-height: 150px; padding: 1rem; } - + .node-metrics { width: 100%; } - + .node-metrics .metrics-container { flex-direction: row; flex-wrap: wrap; } - + .node-metrics .metric-card { flex: 1; min-width: 120px; } } - + @media (max-width: 480px) { .node-info-left { gap: 0.5rem; } - + .node-avatar { width: 64px; height: 64px; padding: 0.5rem; } - + .avatar-image { width: 40px; height: 40px; } - + .node-name { font-size: 1rem; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; } - + .node-address { font-size: 0.8125rem; } - + .node-content-box { min-height: 120px; padding: 0.75rem; } - + .node-metrics .metrics-container { flex-direction: column; gap: 0.75rem; @@ -1275,12 +1616,12 @@ pub fn layout(title: &str, content: Markup) -> Markup { :root { --fs-value: 1.45rem; } - + .node-name { font-size: 0.875rem; } } - + @media (max-width: 480px) { .node-name { font-size: 0.8125rem; @@ -1300,19 +1641,70 @@ pub fn layout(title: &str, content: Markup) -> Markup { body { header { div class="container" { - h1 { "CDK LDK Node" } - p class="subtitle" { "Lightning Network Node Management" } + div class="header-content" { + div class="header-left" { + div class="header-avatar" { + img src="/static/images/nut.png" alt="CDK LDK Node Icon" class="header-avatar-image"; + } + div class="node-info" { + div class="node-status" { + span class="status-indicator status-running" {} + span class="status-text" { "Running" } + } + h1 class="node-title" { "CDK LDK Node" } + span class="node-subtitle" { "Cashu Mint & Lightning Network Node Management" } + } + } + div class="header-right" { + // Right side content can be added here later if needed + } + } } } nav { div class="container" { ul { - li { a href="/" { "Dashboard" } } - li { a href="/balance" { "Lightning" } } - li { a href="/onchain" { "On-chain" } } - li { a href="/invoices" { "Invoices" } } - li { a href="/payments" { "All Payments" } } + li { + a href="/" { + svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" { + path d="M15.6 2.7a10 10 0 1 0 5.7 5.7" {} + circle cx="12" cy="12" r="2" {} + path d="M13.4 10.6 19 5" {} + } + "Dashboard" + } + } + li { + a href="/balance" { + svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" { + path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" {} + } + "Lightning" + } + } + li { + a href="/onchain" { + svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" { + path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" {} + path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" {} + } + "On-chain" + } + } + li { + a href="/payments" { + svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 0.5rem;" { + path d="M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" {} + path d="m16 19 3 3 3-3" {} + path d="M18 12h.01" {} + path d="M19 16v6" {} + path d="M6 12h.01" {} + circle cx="12" cy="12" r="2" {} + } + "All Payments" + } + } } } } @@ -1320,6 +1712,8 @@ pub fn layout(title: &str, content: Markup) -> Markup { main class="container" { (content) } + + } } } diff --git a/crates/cdk-ldk-node/src/web/templates/payments.rs b/crates/cdk-ldk-node/src/web/templates/payments.rs index 84455bd4..479d0764 100644 --- a/crates/cdk-ldk-node/src/web/templates/payments.rs +++ b/crates/cdk-ldk-node/src/web/templates/payments.rs @@ -17,7 +17,7 @@ pub fn payment_list_item( let status_class = match status { "Succeeded" => "status-active", "Failed" => "status-inactive", - "Pending" => "status-badge", + "Pending" => "status-pending", _ => "status-badge", }; diff --git a/crates/cdk-ldk-node/static/images/bg-dark.jpg b/crates/cdk-ldk-node/static/images/bg-dark.jpg index 8cd9b7c2..d96f3453 100644 Binary files a/crates/cdk-ldk-node/static/images/bg-dark.jpg and b/crates/cdk-ldk-node/static/images/bg-dark.jpg differ