pinakes-ui: restyle tasks and statistics components with icons

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ib7e4888602163f828f8aaa9bce2bc5e66a6a6964
This commit is contained in:
raf 2026-02-10 12:39:52 +03:00
commit 445281ea5a
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 367 additions and 261 deletions

View file

@ -1,4 +1,8 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{
FaChartBar, FaCircle, FaClock, FaDatabase, FaFolder, FaLink, FaTags,
};
use super::utils::format_size; use super::utils::format_size;
use crate::client::LibraryStatisticsResponse; use crate::client::LibraryStatisticsResponse;
@ -10,184 +14,228 @@ pub fn Statistics(
on_refresh: EventHandler<()>, on_refresh: EventHandler<()>,
) -> Element { ) -> Element {
rsx! { rsx! {
div { class: "card mb-16", div { class: "statistics-page",
div { class: "card-header", div { class: "card",
h3 { class: "card-title", "Library Statistics" } div { class: "card-header",
button { h3 { class: "card-title", "Library Statistics" }
class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()),
"\u{21bb} Refresh"
}
}
if let Some(ref err) = error {
div { class: "alert alert-error mb-8",
span { "{err}" }
button { button {
class: "btn btn-sm btn-secondary ml-8", class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()), onclick: move |_| on_refresh.call(()),
"Retry" "\u{21bb} Refresh"
} }
} }
}
match stats.as_ref() { if let Some(ref err) = error {
Some(s) => { div { class: "alert alert-error mb-8",
let total_size = format_size(s.total_size_bytes); span { "{err}" }
let avg_size = format_size(s.avg_file_size_bytes); button {
rsx! { class: "btn btn-sm btn-secondary ml-8",
// Overview onclick: move |_| on_refresh.call(()),
div { class: "stats-grid", "Retry"
div { class: "stat-card",
div { class: "stat-value", "{s.total_media}" }
div { class: "stat-label", "Total Media" }
}
div { class: "stat-card",
div { class: "stat-value", "{total_size}" }
div { class: "stat-label", "Total Size" }
}
div { class: "stat-card",
div { class: "stat-value", "{avg_size}" }
div { class: "stat-label", "Avg File Size" }
}
div { class: "stat-card",
div { class: "stat-value", "{s.total_tags}" }
div { class: "stat-label", "Tags" }
}
div { class: "stat-card",
div { class: "stat-value", "{s.total_collections}" }
div { class: "stat-label", "Collections" }
}
div { class: "stat-card",
div { class: "stat-value", "{s.total_duplicates}" }
div { class: "stat-label", "Duplicate Hashes" }
}
} }
}
}
// Media by Type match stats.as_ref() {
Some(s) => {
let total_size = format_size(s.total_size_bytes);
let avg_size = format_size(s.avg_file_size_bytes);
rsx! {
div { class: "stats-overview",
div { class: "stat-card stat-primary",
div { class: "stat-icon",
Icon { icon: FaFolder, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{s.total_media}" }
div { class: "stat-label", "Total Media" }
}
}
div { class: "stat-card stat-success",
div { class: "stat-icon",
Icon { icon: FaDatabase, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{total_size}" }
div { class: "stat-label", "Total Size" }
}
}
div { class: "stat-card stat-info",
div { class: "stat-icon",
Icon { icon: FaChartBar, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{avg_size}" }
div { class: "stat-label", "Average Size" }
}
}
div { class: "stat-card stat-warning",
div { class: "stat-icon",
Icon { icon: FaTags, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{s.total_tags}" }
div { class: "stat-label", "Tags" }
}
}
div { class: "stat-card stat-purple",
div { class: "stat-icon",
Icon { icon: FaCircle, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{s.total_collections}" }
div { class: "stat-label", "Collections" }
}
}
div { class: "stat-card stat-danger",
div { class: "stat-icon",
Icon { icon: FaLink, width: 20, height: 20 }
}
div { class: "stat-content",
div { class: "stat-value", "{s.total_duplicates}" }
div { class: "stat-label", "Duplicates" }
}
}
}
// Storage by Type if !s.media_by_type.is_empty() {
{
let max_count = s.media_by_type.iter().map(|i| i.count).max().unwrap_or(1) as f64;
// Top Tags rsx! {
div { class: "stats-section",
// Top Collections h4 { class: "section-title",
Icon { icon: FaChartBar, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
// Date Range "Media by Type"
}
div { class: "chart-bars",
for item in s.media_by_type.iter() {
{
let percentage = (item.count as f64 / max_count) * 100.0;
let name = item.name.clone();
let count = item.count;
if !s.media_by_type.is_empty() { rsx! {
div { class: "card mt-16", div { key: "{name}", class: "bar-item",
h4 { class: "card-title", "Media by Type" } div { class: "bar-label", "{name}" }
table { class: "table", div { class: "bar-track",
thead { div {
tr { class: "bar-fill bar-primary",
th { "Type" } style: "width: {percentage}%",
th { "Count" } }
}
div { class: "bar-value", "{count}" }
}
}
}
}
} }
} }
tbody { }
for item in s.media_by_type.iter() { }
tr { }
td { "{item.name}" }
td { "{item.count}" } if !s.storage_by_type.is_empty() {
{
let max_size = s.storage_by_type.iter().map(|i| i.count).max().unwrap_or(1) as f64;
rsx! {
div { class: "stats-section",
h4 { class: "section-title",
Icon { icon: FaDatabase, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
"Storage by Type"
}
div { class: "chart-bars",
for item in s.storage_by_type.iter() {
{
let percentage = (item.count as f64 / max_size) * 100.0;
let name = item.name.clone();
let size_str = format_size(item.count);
rsx! {
div { key: "{name}", class: "bar-item",
div { class: "bar-label", "{name}" }
div { class: "bar-track",
div {
class: "bar-fill bar-success",
style: "width: {percentage}%",
}
}
div { class: "bar-value", "{size_str}" }
}
}
}
}
} }
} }
} }
} }
} }
}
if !s.storage_by_type.is_empty() { if !s.top_tags.is_empty() {
div { class: "card mt-16", div { class: "stats-section",
h4 { class: "card-title", "Storage by Type" } h4 { class: "section-title",
table { class: "table", Icon { icon: FaTags, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
thead { "Top Tags"
tr {
th { "Type" }
th { "Size" }
}
} }
tbody { div { class: "tag-list",
for item in s.storage_by_type.iter() {
tr {
td { "{item.name}" }
td { "{format_size(item.count)}" }
}
}
}
}
}
}
if !s.top_tags.is_empty() {
div { class: "card mt-16",
h4 { class: "card-title", "Top Tags" }
table { class: "table",
thead {
tr {
th { "Tag" }
th { "Count" }
}
}
tbody {
for item in s.top_tags.iter() { for item in s.top_tags.iter() {
tr { div { class: "tag-item",
td { "{item.name}" } span { class: "tag-badge", "{item.name}" }
td { "{item.count}" } span { class: "tag-count", "{item.count}" }
} }
} }
} }
} }
} }
}
if !s.top_collections.is_empty() { if !s.top_collections.is_empty() {
div { class: "card mt-16", div { class: "stats-section",
h4 { class: "card-title", "Top Collections" } h4 { class: "section-title",
table { class: "table", Icon { icon: FaCircle, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
thead { "Top Collections"
tr {
th { "Collection" }
th { "Members" }
}
} }
tbody { div { class: "collection-list",
for item in s.top_collections.iter() { for item in s.top_collections.iter() {
tr { div { class: "collection-item",
td { "{item.name}" } Icon { icon: FaFolder, width: 16, height: 16, class: "collection-icon" }
td { "{item.count}" } span { class: "collection-name", "{item.name}" }
span { class: "collection-count", "{item.count}" }
} }
} }
} }
} }
} }
}
div { class: "card mt-16", div { class: "stats-section",
h4 { class: "card-title", "Date Range" } h4 { class: "section-title",
div { class: "stats-grid", Icon { icon: FaClock, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
div { class: "stat-card", "Date Range"
div { class: "stat-value", "{s.oldest_item.as_deref().unwrap_or(\"N/A\")}" }
div { class: "stat-label", "Oldest Item" }
} }
div { class: "stat-card", div { class: "date-range",
div { class: "stat-value", "{s.newest_item.as_deref().unwrap_or(\"N/A\")}" } div { class: "date-item",
div { class: "stat-label", "Newest Item" } Icon { icon: FaClock, width: 16, height: 16 }
div { class: "date-content",
div { class: "date-label", "Oldest Item" }
div { class: "date-value", "{s.oldest_item.as_deref().unwrap_or(\"N/A\")}" }
}
}
div { class: "date-item",
Icon { icon: FaClock, width: 16, height: 16 }
div { class: "date-content",
div { class: "date-label", "Newest Item" }
div { class: "date-value", "{s.newest_item.as_deref().unwrap_or(\"N/A\")}" }
}
}
} }
} }
} }
} }
None => rsx! {
div { class: "empty-state",
div { class: "spinner" }
p { "Loading statistics..." }
}
},
} }
None => rsx! {
div { class: "empty-state",
p { "Loading statistics..." }
}
},
} }
} }
} }

View file

@ -1,4 +1,8 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{
FaArrowsRotate, FaCalendar, FaCircleCheck, FaClock, FaPause, FaPlay,
};
use crate::client::ScheduledTaskResponse; use crate::client::ScheduledTaskResponse;
@ -11,80 +15,134 @@ pub fn Tasks(
on_run_now: EventHandler<String>, on_run_now: EventHandler<String>,
) -> Element { ) -> Element {
rsx! { rsx! {
div { class: "card mb-16", div { class: "tasks-container",
div { class: "card-header", div { class: "card mb-16",
h3 { class: "card-title", "Scheduled Tasks" } div { class: "card-header",
button { h3 { class: "card-title", "Scheduled Tasks" }
class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()),
"\u{21bb} Refresh"
}
}
if let Some(ref err) = error {
div { class: "alert alert-error mb-8",
span { "{err}" }
button { button {
class: "btn btn-sm btn-secondary ml-8", class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()), onclick: move |_| on_refresh.call(()),
"Retry" Icon { icon: FaArrowsRotate, width: 14, height: 14 }
" Refresh"
} }
} }
}
if tasks.is_empty() { if let Some(ref err) = error {
div { class: "empty-state", div { class: "alert alert-error mb-8",
p { "No scheduled tasks configured." } span { "{err}" }
} button {
} else { class: "btn btn-sm btn-secondary ml-8",
table { class: "table", onclick: move |_| on_refresh.call(()),
thead { "Retry"
tr {
th { "Enabled" }
th { "Name" }
th { "Schedule" }
th { "Last Run" }
th { "Next Run" }
th { "Status" }
th { "Actions" }
} }
} }
tbody { }
if tasks.is_empty() {
div { class: "empty-state",
div { class: "empty-icon",
Icon { icon: FaCalendar, width: 48, height: 48 }
}
p { "No scheduled tasks configured." }
p { class: "text-muted", "Tasks will appear here once configured on the server." }
}
} else {
div { class: "tasks-grid",
for task in tasks.iter() { for task in tasks.iter() {
{ {
let task_id_toggle = task.id.clone(); let task_id_toggle = task.id.clone();
let task_id_run = task.id.clone(); let task_id_run = task.id.clone();
let last_run = task.last_run.clone().unwrap_or_else(|| "-".to_string()); let last_run = task.last_run.clone().unwrap_or_else(|| "Never".to_string());
let next_run = task.next_run.clone().unwrap_or_else(|| "-".to_string()); let next_run = task.next_run.clone().unwrap_or_else(|| "Not scheduled".to_string());
let last_status = task.last_status.clone().unwrap_or_else(|| "-".to_string()); let last_status = task.last_status.clone().unwrap_or_else(|| "No runs yet".to_string());
let is_enabled = task.enabled;
let task_name = task.name.clone();
let schedule = task.schedule.clone();
rsx! { rsx! {
tr { div {
td { class: if is_enabled { "task-card task-card-enabled" } else { "task-card task-card-disabled" },
if task.enabled {
span { class: "badge badge-success", "\u{2713}" } // Header with status and actions
} else { div { class: "task-card-header",
span { class: "badge badge-muted", "\u{2715}" } div { class: "task-header-left",
div { class: "task-name", "{task_name}" }
div { class: "task-schedule",
span { class: "schedule-icon",
Icon { icon: FaClock, width: 14, height: 14 }
}
"{schedule}"
}
}
div { class: "task-status-badge",
if is_enabled {
span { class: "status-badge status-enabled",
span { class: "status-dot" }
"Active"
}
} else {
span { class: "status-badge status-disabled",
span { class: "status-dot" }
"Disabled"
}
}
} }
} }
td { "{task.name}" }
td { "{task.schedule}" } // Task info grid
td { "{last_run}" } div { class: "task-info-grid",
td { "{next_run}" } div { class: "task-info-item",
td { "{last_status}" } div { class: "task-info-icon",
td { Icon { icon: FaClock, width: 16, height: 16 }
}
div { class: "task-info-content",
div { class: "task-info-label", "Last Run" }
div { class: "task-info-value", "{last_run}" }
}
}
div { class: "task-info-item",
div { class: "task-info-icon",
Icon { icon: FaClock, width: 16, height: 16 }
}
div { class: "task-info-content",
div { class: "task-info-label", "Next Run" }
div { class: "task-info-value", "{next_run}" }
}
}
div { class: "task-info-item",
div { class: "task-info-icon",
Icon { icon: FaCircleCheck, width: 16, height: 16 }
}
div { class: "task-info-content",
div { class: "task-info-label", "Last Status" }
div { class: "task-info-value", "{last_status}" }
}
}
}
// Actions
div { class: "task-card-actions",
button { button {
class: "btn btn-sm btn-secondary mr-8", class: if is_enabled { "btn btn-sm btn-secondary" } else { "btn btn-sm btn-primary" },
onclick: move |_| on_toggle.call(task_id_toggle.clone()), onclick: move |_| on_toggle.call(task_id_toggle.clone()),
if task.enabled { if is_enabled {
"Disable" span {
Icon { icon: FaPause, width: 14, height: 14 }
" Disable"
}
} else { } else {
"Enable" span {
Icon { icon: FaPlay, width: 14, height: 14 }
" Enable"
}
} }
} }
button { button {
class: "btn btn-sm btn-primary", class: "btn btn-sm btn-primary",
onclick: move |_| on_run_now.call(task_id_run.clone()), onclick: move |_| on_run_now.call(task_id_run.clone()),
"Run Now" disabled: !is_enabled,
Icon { icon: FaPlay, width: 14, height: 14 }
" Run Now"
} }
} }
} }

View file

@ -53,7 +53,7 @@ body {
outline-offset: 2px; outline-offset: 2px;
} }
/* ── Layout ── */ /* Layout */
.app { .app {
display: flex; display: flex;
height: 100vh; height: 100vh;
@ -233,7 +233,7 @@ body {
padding: 20px; padding: 20px;
} }
/* ── Table ── */ /* Table */
.data-table { .data-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -282,7 +282,7 @@ body {
border-bottom: none; border-bottom: none;
} }
/* ── Buttons ── */ /* Buttons */
.btn { .btn {
padding: 5px 12px; padding: 5px 12px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
@ -349,7 +349,7 @@ body {
} }
.btn-icon:hover { color: var(--text-0); } .btn-icon:hover { color: var(--text-0); }
/* ── Cards ── */ /* Cards */
.card { .card {
background: var(--bg-2); background: var(--bg-2);
border: 1px solid var(--border); border: 1px solid var(--border);
@ -369,7 +369,7 @@ body {
font-weight: 600; font-weight: 600;
} }
/* ── Forms ── */ /* Forms */
input[type="text"], textarea, select { input[type="text"], textarea, select {
padding: 6px 10px; padding: 6px 10px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
@ -412,7 +412,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
flex: 1; flex: 1;
} }
/* ── Toast ── */ /* Toast */
.toast { .toast {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
@ -437,7 +437,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
/* ── Detail ── */ /* Detail */
.detail-actions { .detail-actions {
display: flex; display: flex;
gap: 6px; gap: 6px;
@ -482,7 +482,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
color: var(--text-1); color: var(--text-1);
} }
/* ── Stats ── */ /* Stats */
.statistics-page { .statistics-page {
padding: 20px; padding: 20px;
} }
@ -692,7 +692,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
font-family: 'Menlo', 'Monaco', 'Courier New', monospace; font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
} }
/* ── Tasks ── */ /* Tasks */
.tasks-grid { .tasks-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
@ -875,7 +875,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
.type-text { background: rgba(200, 160, 36, 0.1); color: #c4a840; } .type-text { background: rgba(200, 160, 36, 0.1); color: #c4a840; }
.type-other { background: rgba(128, 128, 160, 0.08); color: var(--text-2); } .type-other { background: rgba(128, 128, 160, 0.08); color: var(--text-2); }
/* ── Tags ── */ /* Tags */
.tag-list { display: flex; flex-wrap: wrap; gap: 4px; } .tag-list { display: flex; flex-wrap: wrap; gap: 4px; }
.tag-badge { .tag-badge {
@ -909,7 +909,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
cursor: pointer; cursor: pointer;
} }
/* ── Empty state ── */ /* Empty state */
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 48px 16px; padding: 48px 16px;
@ -936,7 +936,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
line-height: 1.5; line-height: 1.5;
} }
/* ── Settings ── */ /* Settings */
.settings-section { margin-bottom: 24px; } .settings-section { margin-bottom: 24px; }
.section-title { .section-title {
@ -1016,7 +1016,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
.info-label { color: var(--text-1); font-weight: 500; } .info-label { color: var(--text-1); font-weight: 500; }
.info-value { color: var(--text-0); } .info-value { color: var(--text-0); }
/* ── Scrollbar ── */ /* Scrollbar */
::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; } ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }
@ -1032,7 +1032,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
scrollbar-color: rgba(255,255,255,0.08) transparent; scrollbar-color: rgba(255,255,255,0.08) transparent;
} }
/* ── Import Tabs ── */ /* Import Tabs */
.import-tabs { .import-tabs {
display: flex; display: flex;
gap: 0; gap: 0;
@ -1061,7 +1061,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
border-bottom-color: var(--accent); border-bottom-color: var(--accent);
} }
/* ── Batch Actions ── */ /* Batch Actions */
.batch-actions { .batch-actions {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1076,13 +1076,13 @@ input[type="text"]:focus, textarea:focus, select:focus {
color: var(--accent-text); color: var(--accent-text);
} }
/* ── Action badges (audit) ── */ /* Action badges (audit) */
.action-danger { .action-danger {
background: rgba(228, 88, 88, 0.1); background: rgba(228, 88, 88, 0.1);
color: #d47070; color: #d47070;
} }
/* ── Tag hierarchy ── */ /* Tag hierarchy */
.tag-group { .tag-group {
margin-bottom: 6px; margin-bottom: 6px;
} }
@ -1095,7 +1095,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
gap: 4px; gap: 4px;
} }
/* ── Detail field inputs ── */ /* Detail field inputs */
.detail-field input[type="text"], .detail-field input[type="text"],
.detail-field textarea, .detail-field textarea,
.detail-field select { .detail-field select {
@ -1108,7 +1108,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
resize: vertical; resize: vertical;
} }
/* ── Checkbox ── */ /* Checkbox */
input[type="checkbox"] { input[type="checkbox"] {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
@ -1192,7 +1192,7 @@ input[type="number"]:focus {
border-color: var(--accent); border-color: var(--accent);
} }
/* ── Select ── */ /* Select */
select { select {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
@ -1203,7 +1203,7 @@ select {
min-width: 100px; min-width: 100px;
} }
/* ── Code ── */ /* Code */
code { code {
padding: 1px 5px; padding: 1px 5px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
@ -1216,7 +1216,7 @@ code {
ul { list-style: none; padding: 0; } ul { list-style: none; padding: 0; }
ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); } ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
/* ── Status indicator ── */ /* Status indicator */
.status-indicator { .status-indicator {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1262,7 +1262,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
overflow: visible; overflow: visible;
} }
/* ── Modal ── */ /* Modal */
.modal-overlay { .modal-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -1308,7 +1308,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 6px; gap: 6px;
} }
/* ── Saved Searches ── */ /* Saved Searches */
.saved-searches-list { .saved-searches-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1363,7 +1363,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0; margin: 0;
} }
/* ── Offline banner ── */ /* Offline banner */
.offline-banner { .offline-banner {
background: rgba(228, 88, 88, 0.06); background: rgba(228, 88, 88, 0.06);
border: 1px solid rgba(228, 88, 88, 0.2); border: 1px solid rgba(228, 88, 88, 0.2);
@ -1382,7 +1382,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
flex-shrink: 0; flex-shrink: 0;
} }
/* ── Utility ── */ /* Utility */
.flex-row { display: flex; align-items: center; gap: 8px; } .flex-row { display: flex; align-items: center; gap: 8px; }
.flex-between { display: flex; justify-content: space-between; align-items: center; } .flex-between { display: flex; justify-content: space-between; align-items: center; }
.mb-16 { margin-bottom: 16px; } .mb-16 { margin-bottom: 16px; }
@ -1391,7 +1391,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
.text-sm { font-size: 11px; } .text-sm { font-size: 11px; }
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 12px; } .mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 12px; }
/* ── Filter bar ── */ /* Filter bar */
.filter-bar { .filter-bar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1575,7 +1575,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
display: block; display: block;
} }
/* ── Form label row ── */ /* Form label row */
.form-label-row { .form-label-row {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1587,7 +1587,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-bottom: 0; margin-bottom: 0;
} }
/* ── Read-only banner ── */ /* Read-only banner */
.readonly-banner { .readonly-banner {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1601,7 +1601,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--warning); color: var(--warning);
} }
/* ── Config path ── */ /* Config path */
.config-path { .config-path {
font-size: 11px; font-size: 11px;
color: var(--text-2); color: var(--text-2);
@ -1613,7 +1613,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
} }
/* ── Settings cards ── */ /* Settings cards */
.settings-card { .settings-card {
background: var(--bg-2); background: var(--bg-2);
border: 1px solid var(--border); border: 1px solid var(--border);
@ -1656,7 +1656,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--error); color: var(--error);
} }
/* ── Disabled button ── */ /* Disabled button */
.btn:disabled, .btn[disabled] { .btn:disabled, .btn[disabled] {
opacity: 0.4; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
@ -1671,7 +1671,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
cursor: help; cursor: help;
} }
/* ── Library Toolbar ── */ /* Library Toolbar */
.library-toolbar { .library-toolbar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -1694,7 +1694,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 10px; gap: 10px;
} }
/* ── View Toggle ── */ /* View Toggle */
.view-toggle { .view-toggle {
display: flex; display: flex;
border: 1px solid var(--border); border: 1px solid var(--border);
@ -1727,7 +1727,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--accent-text); color: var(--accent-text);
} }
/* ── Sort & Page Size Controls ── */ /* Sort & Page Size Controls */
.sort-control select, .sort-control select,
.page-size-control select { .page-size-control select {
padding: 4px 24px 4px 8px; padding: 4px 24px 4px 8px;
@ -1741,7 +1741,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 4px; gap: 4px;
} }
/* ── Media Grid ── */ /* Media Grid */
.media-grid { .media-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
@ -1768,7 +1768,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
box-shadow: 0 0 0 1px var(--accent); box-shadow: 0 0 0 1px var(--accent);
} }
/* ── Card Checkbox ── */ /* Card Checkbox */
.card-checkbox { .card-checkbox {
position: absolute; position: absolute;
top: 6px; top: 6px;
@ -1790,7 +1790,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.5)); filter: drop-shadow(0 1px 2px rgba(0,0,0,0.5));
} }
/* ── Card Thumbnail ── */ /* Card Thumbnail */
.card-thumbnail { .card-thumbnail {
width: 100%; width: 100%;
aspect-ratio: 1; aspect-ratio: 1;
@ -1827,7 +1827,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
z-index: 0; z-index: 0;
} }
/* ── Card Info ── */ /* Card Info */
.card-info { .card-info {
padding: 8px 10px; padding: 8px 10px;
} }
@ -1854,7 +1854,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 10px; font-size: 10px;
} }
/* ── Table Thumbnail ── */ /* Table Thumbnail */
.table-thumb-cell { .table-thumb-cell {
width: 36px; width: 36px;
padding: 4px 6px !important; padding: 4px 6px !important;
@ -1889,7 +1889,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
z-index: 0; z-index: 0;
} }
/* ── Type Filter Row ── */ /* Type Filter Row */
.type-filter-row { .type-filter-row {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1922,7 +1922,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border-color: var(--accent); border-color: var(--accent);
} }
/* ── Library Stats Row ── */ /* Library Stats Row */
.library-stats { .library-stats {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -1931,7 +1931,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 11px; font-size: 11px;
} }
/* ── Sortable Table Headers ── */ /* Sortable Table Headers */
.sortable-header { .sortable-header {
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
@ -1942,7 +1942,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--accent-text); color: var(--accent-text);
} }
/* ── Card extra info ── */ /* Card extra info */
.card-title, .card-title,
.card-artist { .card-artist {
font-size: 10px; font-size: 10px;
@ -1952,7 +1952,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
line-height: 1.3; line-height: 1.3;
} }
/* ── Pagination ── */ /* Pagination */
.pagination { .pagination {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1975,7 +1975,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
user-select: none; user-select: none;
} }
/* ── Loading indicator ── */ /* Loading indicator */
.loading-overlay { .loading-overlay {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2017,7 +2017,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
flex-shrink: 0; flex-shrink: 0;
} }
/* ── Toast container (stacked) ── */ /* Toast container (stacked) */
.toast-container { .toast-container {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
@ -2034,7 +2034,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
transform: none; transform: none;
} }
/* ── Nav badge ── */ /* Nav badge */
.nav-badge { .nav-badge {
margin-left: auto; margin-left: auto;
font-size: 10px; font-size: 10px;
@ -2048,7 +2048,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
/* ── Detail preview ── */ /* Detail preview */
.detail-preview { .detail-preview {
margin-bottom: 16px; margin-bottom: 16px;
background: var(--bg-0); background: var(--bg-0);
@ -2086,7 +2086,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0 auto; margin: 0 auto;
} }
/* ── Action badge styles (audit) ── */ /* Action badge styles (audit) */
.action-updated { .action-updated {
background: rgba(59, 120, 200, 0.1); background: rgba(59, 120, 200, 0.1);
color: #6ca0d4; color: #6ca0d4;
@ -2112,7 +2112,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--text-2); color: var(--text-2);
} }
/* ── Audit controls ── */ /* Audit controls */
.audit-controls { .audit-controls {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2126,7 +2126,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
background: var(--bg-2); background: var(--bg-2);
} }
/* ── Clickable elements ── */ /* Clickable elements */
.clickable { .clickable {
cursor: pointer; cursor: pointer;
color: var(--accent-text); color: var(--accent-text);
@ -2144,7 +2144,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
background: rgba(255,255,255,0.03); background: rgba(255,255,255,0.03);
} }
/* ── Progress bar ── */ /* Progress bar */
.progress-bar { .progress-bar {
width: 100%; width: 100%;
height: 8px; height: 8px;
@ -2171,7 +2171,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
100% { transform: translateX(400%); } 100% { transform: translateX(400%); }
} }
/* ── Import status panel ── */ /* Import status panel */
.import-status-panel { .import-status-panel {
background: var(--bg-2); background: var(--bg-2);
border: 1px solid var(--accent); border: 1px solid var(--accent);
@ -2238,7 +2238,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--text-2); color: var(--text-2);
} }
/* ── Sidebar import progress ── */ /* Sidebar import progress */
.sidebar-import-progress { .sidebar-import-progress {
padding: 8px 12px; padding: 8px 12px;
background: var(--bg-2); background: var(--bg-2);
@ -2276,7 +2276,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
display: none; display: none;
} }
/* ── Tag confirmation ── */ /* Tag confirmation */
.tag-confirm-delete { .tag-confirm-delete {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -2305,7 +2305,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
text-decoration: underline; text-decoration: underline;
} }
/* ── Help overlay ── */ /* Help overlay */
.help-overlay { .help-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -2381,14 +2381,14 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
background: rgba(255,255,255,0.06); background: rgba(255,255,255,0.06);
} }
/* ── Add media modal (collections) ── */ /* Add media modal (collections) */
.modal.wide { .modal.wide {
max-width: 600px; max-width: 600px;
max-height: 70vh; max-height: 70vh;
overflow-y: auto; overflow-y: auto;
} }
/* ── Database management ── */ /* Database management */
.db-actions { .db-actions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -2428,7 +2428,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border: 1px solid rgba(228, 88, 88, 0.25); border: 1px solid rgba(228, 88, 88, 0.25);
} }
/* ── Library select-all banner ── */ /* Library select-all banner */
.select-all-banner { .select-all-banner {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2457,7 +2457,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--text-0); color: var(--text-0);
} }
/* ── Media Player ── */ /* Media Player */
.media-player { .media-player {
position: relative; position: relative;
background: var(--bg-0); background: var(--bg-0);
@ -2610,7 +2610,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border: none; border: none;
} }
/* ── Image Viewer ── */ /* Image Viewer */
.image-viewer-overlay { .image-viewer-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -2687,7 +2687,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
-webkit-user-drag: none; -webkit-user-drag: none;
} }
/* ── Markdown Viewer ── */ /* Markdown Viewer */
.markdown-viewer { .markdown-viewer {
padding: 16px; padding: 16px;
text-align: left; text-align: left;
@ -2861,7 +2861,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 0.8em; font-size: 0.8em;
} }
/* ── Frontmatter Card ── */ /* Frontmatter Card */
.frontmatter-card { .frontmatter-card {
max-width: 800px; max-width: 800px;
background: var(--bg-2); background: var(--bg-2);
@ -2891,7 +2891,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0; margin: 0;
} }
/* ── Duplicates ── */ /* Duplicates */
.duplicates-view { .duplicates-view {
padding: 0; padding: 0;
} }
@ -3058,7 +3058,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-weight: 600; font-weight: 600;
} }
/* ── Login ── */ /* Login */
.login-container { .login-container {
display: flex; display: flex;
align-items: center; align-items: center;
@ -3113,7 +3113,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-top: 4px; margin-top: 4px;
} }
/* ── User Info (sidebar) ── */ /* User Info (sidebar) */
.user-info { .user-info {
display: flex; display: flex;
align-items: center; align-items: center;
@ -3156,7 +3156,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
.role-editor { background: rgba(34, 160, 80, 0.1); color: #5cb97a; } .role-editor { background: rgba(34, 160, 80, 0.1); color: #5cb97a; }
.role-viewer { background: rgba(59, 120, 200, 0.1); color: #6ca0d4; } .role-viewer { background: rgba(59, 120, 200, 0.1); color: #6ca0d4; }
/* ── Settings fields ── */ /* Settings fields */
.settings-field { .settings-field {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -3175,7 +3175,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
padding-top: 4px; padding-top: 4px;
} }
/* ── Detail no preview ── */ /* Detail no preview */
.detail-no-preview { .detail-no-preview {
padding: 32px 16px; padding: 32px 16px;
text-align: center; text-align: center;
@ -3185,7 +3185,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 12px; gap: 12px;
} }
/* ── Light Theme ── */ /* Light Theme */
.theme-light { .theme-light {
--bg-0: #f5f5f7; --bg-0: #f5f5f7;
--bg-1: #eeeef0; --bg-1: #eeeef0;
@ -3217,7 +3217,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
background: rgba(0, 0, 0, 0.05); background: rgba(0, 0, 0, 0.05);
} }
/* ── Skeleton Loading States ── */ /* Skeleton Loading States */
@keyframes skeleton-pulse { @keyframes skeleton-pulse {
0% { opacity: 0.6; } 0% { opacity: 0.6; }
50% { opacity: 0.3; } 50% { opacity: 0.3; }
@ -3307,7 +3307,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 0.9rem; font-size: 0.9rem;
} }
/* ── Breadcrumb ── */ /* Breadcrumb */
.breadcrumb { .breadcrumb {
display: flex; display: flex;
align-items: center; align-items: center;
@ -3337,7 +3337,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-weight: 500; font-weight: 500;
} }
/* ── Queue Panel ── */ /* Queue Panel */
.queue-panel { .queue-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -4135,7 +4135,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-top: 8px; margin-top: 8px;
} }
/* ── Wikilink Styles (in markdown) ── */ /* Wikilink Styles (in markdown) */
.wikilink { .wikilink {
color: var(--accent-text); color: var(--accent-text);
text-decoration: none; text-decoration: none;
@ -4160,7 +4160,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
cursor: default; cursor: default;
} }
/* ── Light theme adjustments for links and graph ── */ /* Light theme adjustments for links and graph */
.theme-light .graph-nodes .graph-node text { .theme-light .graph-nodes .graph-node text {
fill: var(--text-0); fill: var(--text-0);
} }