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_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{
FaChartBar, FaCircle, FaClock, FaDatabase, FaFolder, FaLink, FaTags,
};
use super::utils::format_size;
use crate::client::LibraryStatisticsResponse;
@ -10,184 +14,228 @@ pub fn Statistics(
on_refresh: EventHandler<()>,
) -> Element {
rsx! {
div { class: "card mb-16",
div { class: "card-header",
h3 { class: "card-title", "Library Statistics" }
button {
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}" }
div { class: "statistics-page",
div { class: "card",
div { class: "card-header",
h3 { class: "card-title", "Library Statistics" }
button {
class: "btn btn-sm btn-secondary ml-8",
class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()),
"Retry"
"\u{21bb} Refresh"
}
}
}
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! {
// Overview
div { class: "stats-grid",
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" }
}
if let Some(ref err) = error {
div { class: "alert alert-error mb-8",
span { "{err}" }
button {
class: "btn btn-sm btn-secondary ml-8",
onclick: move |_| on_refresh.call(()),
"Retry"
}
}
}
// 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
// Top Collections
// Date Range
if !s.media_by_type.is_empty() {
div { class: "card mt-16",
h4 { class: "card-title", "Media by Type" }
table { class: "table",
thead {
tr {
th { "Type" }
th { "Count" }
rsx! {
div { class: "stats-section",
h4 { class: "section-title",
Icon { icon: FaChartBar, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
"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;
rsx! {
div { key: "{name}", class: "bar-item",
div { class: "bar-label", "{name}" }
div { class: "bar-track",
div {
class: "bar-fill bar-primary",
style: "width: {percentage}%",
}
}
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() {
div { class: "card mt-16",
h4 { class: "card-title", "Storage by Type" }
table { class: "table",
thead {
tr {
th { "Type" }
th { "Size" }
}
if !s.top_tags.is_empty() {
div { class: "stats-section",
h4 { class: "section-title",
Icon { icon: FaTags, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
"Top Tags"
}
tbody {
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 {
div { class: "tag-list",
for item in s.top_tags.iter() {
tr {
td { "{item.name}" }
td { "{item.count}" }
div { class: "tag-item",
span { class: "tag-badge", "{item.name}" }
span { class: "tag-count", "{item.count}" }
}
}
}
}
}
}
if !s.top_collections.is_empty() {
div { class: "card mt-16",
h4 { class: "card-title", "Top Collections" }
table { class: "table",
thead {
tr {
th { "Collection" }
th { "Members" }
}
if !s.top_collections.is_empty() {
div { class: "stats-section",
h4 { class: "section-title",
Icon { icon: FaCircle, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
"Top Collections"
}
tbody {
div { class: "collection-list",
for item in s.top_collections.iter() {
tr {
td { "{item.name}" }
td { "{item.count}" }
div { class: "collection-item",
Icon { icon: FaFolder, width: 16, height: 16, class: "collection-icon" }
span { class: "collection-name", "{item.name}" }
span { class: "collection-count", "{item.count}" }
}
}
}
}
}
}
div { class: "card mt-16",
h4 { class: "card-title", "Date Range" }
div { class: "stats-grid",
div { class: "stat-card",
div { class: "stat-value", "{s.oldest_item.as_deref().unwrap_or(\"N/A\")}" }
div { class: "stat-label", "Oldest Item" }
div { class: "stats-section",
h4 { class: "section-title",
Icon { icon: FaClock, width: 16, height: 16, style: "margin-right: 8px; vertical-align: middle;" }
"Date Range"
}
div { class: "stat-card",
div { class: "stat-value", "{s.newest_item.as_deref().unwrap_or(\"N/A\")}" }
div { class: "stat-label", "Newest Item" }
div { class: "date-range",
div { class: "date-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_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{
FaArrowsRotate, FaCalendar, FaCircleCheck, FaClock, FaPause, FaPlay,
};
use crate::client::ScheduledTaskResponse;
@ -11,80 +15,134 @@ pub fn Tasks(
on_run_now: EventHandler<String>,
) -> Element {
rsx! {
div { class: "card mb-16",
div { class: "card-header",
h3 { class: "card-title", "Scheduled Tasks" }
button {
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}" }
div { class: "tasks-container",
div { class: "card mb-16",
div { class: "card-header",
h3 { class: "card-title", "Scheduled Tasks" }
button {
class: "btn btn-sm btn-secondary ml-8",
class: "btn btn-sm btn-secondary",
onclick: move |_| on_refresh.call(()),
"Retry"
Icon { icon: FaArrowsRotate, width: 14, height: 14 }
" Refresh"
}
}
}
if tasks.is_empty() {
div { class: "empty-state",
p { "No scheduled tasks configured." }
}
} else {
table { class: "table",
thead {
tr {
th { "Enabled" }
th { "Name" }
th { "Schedule" }
th { "Last Run" }
th { "Next Run" }
th { "Status" }
th { "Actions" }
if let Some(ref err) = error {
div { class: "alert alert-error mb-8",
span { "{err}" }
button {
class: "btn btn-sm btn-secondary ml-8",
onclick: move |_| on_refresh.call(()),
"Retry"
}
}
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() {
{
let task_id_toggle = task.id.clone();
let task_id_run = task.id.clone();
let last_run = task.last_run.clone().unwrap_or_else(|| "-".to_string());
let next_run = task.next_run.clone().unwrap_or_else(|| "-".to_string());
let last_status = task.last_status.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(|| "Not scheduled".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! {
tr {
td {
if task.enabled {
span { class: "badge badge-success", "\u{2713}" }
} else {
span { class: "badge badge-muted", "\u{2715}" }
div {
class: if is_enabled { "task-card task-card-enabled" } else { "task-card task-card-disabled" },
// Header with status and actions
div { class: "task-card-header",
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}" }
td { "{last_run}" }
td { "{next_run}" }
td { "{last_status}" }
td {
// Task info grid
div { class: "task-info-grid",
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", "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 {
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()),
if task.enabled {
"Disable"
if is_enabled {
span {
Icon { icon: FaPause, width: 14, height: 14 }
" Disable"
}
} else {
"Enable"
span {
Icon { icon: FaPlay, width: 14, height: 14 }
" Enable"
}
}
}
button {
class: "btn btn-sm btn-primary",
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;
}
/* ── Layout ── */
/* Layout */
.app {
display: flex;
height: 100vh;
@ -233,7 +233,7 @@ body {
padding: 20px;
}
/* ── Table ── */
/* Table */
.data-table {
width: 100%;
border-collapse: collapse;
@ -282,7 +282,7 @@ body {
border-bottom: none;
}
/* ── Buttons ── */
/* Buttons */
.btn {
padding: 5px 12px;
border-radius: var(--radius-sm);
@ -349,7 +349,7 @@ body {
}
.btn-icon:hover { color: var(--text-0); }
/* ── Cards ── */
/* Cards */
.card {
background: var(--bg-2);
border: 1px solid var(--border);
@ -369,7 +369,7 @@ body {
font-weight: 600;
}
/* ── Forms ── */
/* Forms */
input[type="text"], textarea, select {
padding: 6px 10px;
border-radius: var(--radius-sm);
@ -412,7 +412,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
flex: 1;
}
/* ── Toast ── */
/* Toast */
.toast {
position: fixed;
bottom: 16px;
@ -437,7 +437,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
to { opacity: 1; transform: translateY(0); }
}
/* ── Detail ── */
/* Detail */
.detail-actions {
display: flex;
gap: 6px;
@ -482,7 +482,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
color: var(--text-1);
}
/* ── Stats ── */
/* Stats */
.statistics-page {
padding: 20px;
}
@ -692,7 +692,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
}
/* ── Tasks ── */
/* Tasks */
.tasks-grid {
display: grid;
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-other { background: rgba(128, 128, 160, 0.08); color: var(--text-2); }
/* ── Tags ── */
/* Tags */
.tag-list { display: flex; flex-wrap: wrap; gap: 4px; }
.tag-badge {
@ -909,7 +909,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
cursor: pointer;
}
/* ── Empty state ── */
/* Empty state */
.empty-state {
text-align: center;
padding: 48px 16px;
@ -936,7 +936,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
line-height: 1.5;
}
/* ── Settings ── */
/* Settings */
.settings-section { margin-bottom: 24px; }
.section-title {
@ -1016,7 +1016,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
.info-label { color: var(--text-1); font-weight: 500; }
.info-value { color: var(--text-0); }
/* ── Scrollbar ── */
/* Scrollbar */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-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;
}
/* ── Import Tabs ── */
/* Import Tabs */
.import-tabs {
display: flex;
gap: 0;
@ -1061,7 +1061,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
border-bottom-color: var(--accent);
}
/* ── Batch Actions ── */
/* Batch Actions */
.batch-actions {
display: flex;
align-items: center;
@ -1076,13 +1076,13 @@ input[type="text"]:focus, textarea:focus, select:focus {
color: var(--accent-text);
}
/* ── Action badges (audit) ── */
/* Action badges (audit) */
.action-danger {
background: rgba(228, 88, 88, 0.1);
color: #d47070;
}
/* ── Tag hierarchy ── */
/* Tag hierarchy */
.tag-group {
margin-bottom: 6px;
}
@ -1095,7 +1095,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
gap: 4px;
}
/* ── Detail field inputs ── */
/* Detail field inputs */
.detail-field input[type="text"],
.detail-field textarea,
.detail-field select {
@ -1108,7 +1108,7 @@ input[type="text"]:focus, textarea:focus, select:focus {
resize: vertical;
}
/* ── Checkbox ── */
/* Checkbox */
input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
@ -1192,7 +1192,7 @@ input[type="number"]:focus {
border-color: var(--accent);
}
/* ── Select ── */
/* Select */
select {
appearance: none;
-webkit-appearance: none;
@ -1203,7 +1203,7 @@ select {
min-width: 100px;
}
/* ── Code ── */
/* Code */
code {
padding: 1px 5px;
border-radius: var(--radius-sm);
@ -1216,7 +1216,7 @@ code {
ul { list-style: none; padding: 0; }
ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
/* ── Status indicator ── */
/* Status indicator */
.status-indicator {
display: flex;
align-items: center;
@ -1262,7 +1262,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
overflow: visible;
}
/* ── Modal ── */
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
@ -1308,7 +1308,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 6px;
}
/* ── Saved Searches ── */
/* Saved Searches */
.saved-searches-list {
display: flex;
flex-direction: column;
@ -1363,7 +1363,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0;
}
/* ── Offline banner ── */
/* Offline banner */
.offline-banner {
background: rgba(228, 88, 88, 0.06);
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;
}
/* ── Utility ── */
/* Utility */
.flex-row { display: flex; align-items: center; gap: 8px; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.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; }
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 12px; }
/* ── Filter bar ── */
/* Filter bar */
.filter-bar {
display: flex;
flex-direction: column;
@ -1575,7 +1575,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
display: block;
}
/* ── Form label row ── */
/* Form label row */
.form-label-row {
display: flex;
align-items: center;
@ -1587,7 +1587,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-bottom: 0;
}
/* ── Read-only banner ── */
/* Read-only banner */
.readonly-banner {
display: flex;
align-items: center;
@ -1601,7 +1601,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--warning);
}
/* ── Config path ── */
/* Config path */
.config-path {
font-size: 11px;
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);
}
/* ── Settings cards ── */
/* Settings cards */
.settings-card {
background: var(--bg-2);
border: 1px solid var(--border);
@ -1656,7 +1656,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--error);
}
/* ── Disabled button ── */
/* Disabled button */
.btn:disabled, .btn[disabled] {
opacity: 0.4;
cursor: not-allowed;
@ -1671,7 +1671,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
cursor: help;
}
/* ── Library Toolbar ── */
/* Library Toolbar */
.library-toolbar {
display: flex;
justify-content: space-between;
@ -1694,7 +1694,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 10px;
}
/* ── View Toggle ── */
/* View Toggle */
.view-toggle {
display: flex;
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);
}
/* ── Sort & Page Size Controls ── */
/* Sort & Page Size Controls */
.sort-control select,
.page-size-control select {
padding: 4px 24px 4px 8px;
@ -1741,7 +1741,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 4px;
}
/* ── Media Grid ── */
/* Media Grid */
.media-grid {
display: grid;
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);
}
/* ── Card Checkbox ── */
/* Card Checkbox */
.card-checkbox {
position: absolute;
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));
}
/* ── Card Thumbnail ── */
/* Card Thumbnail */
.card-thumbnail {
width: 100%;
aspect-ratio: 1;
@ -1827,7 +1827,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
z-index: 0;
}
/* ── Card Info ── */
/* Card Info */
.card-info {
padding: 8px 10px;
}
@ -1854,7 +1854,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 10px;
}
/* ── Table Thumbnail ── */
/* Table Thumbnail */
.table-thumb-cell {
width: 36px;
padding: 4px 6px !important;
@ -1889,7 +1889,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
z-index: 0;
}
/* ── Type Filter Row ── */
/* Type Filter Row */
.type-filter-row {
display: flex;
align-items: center;
@ -1922,7 +1922,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border-color: var(--accent);
}
/* ── Library Stats Row ── */
/* Library Stats Row */
.library-stats {
display: flex;
justify-content: space-between;
@ -1931,7 +1931,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 11px;
}
/* ── Sortable Table Headers ── */
/* Sortable Table Headers */
.sortable-header {
cursor: pointer;
user-select: none;
@ -1942,7 +1942,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--accent-text);
}
/* ── Card extra info ── */
/* Card extra info */
.card-title,
.card-artist {
font-size: 10px;
@ -1952,7 +1952,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
line-height: 1.3;
}
/* ── Pagination ── */
/* Pagination */
.pagination {
display: flex;
align-items: center;
@ -1975,7 +1975,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
user-select: none;
}
/* ── Loading indicator ── */
/* Loading indicator */
.loading-overlay {
display: flex;
align-items: center;
@ -2017,7 +2017,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
flex-shrink: 0;
}
/* ── Toast container (stacked) ── */
/* Toast container (stacked) */
.toast-container {
position: fixed;
bottom: 16px;
@ -2034,7 +2034,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
transform: none;
}
/* ── Nav badge ── */
/* Nav badge */
.nav-badge {
margin-left: auto;
font-size: 10px;
@ -2048,7 +2048,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-variant-numeric: tabular-nums;
}
/* ── Detail preview ── */
/* Detail preview */
.detail-preview {
margin-bottom: 16px;
background: var(--bg-0);
@ -2086,7 +2086,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0 auto;
}
/* ── Action badge styles (audit) ── */
/* Action badge styles (audit) */
.action-updated {
background: rgba(59, 120, 200, 0.1);
color: #6ca0d4;
@ -2112,7 +2112,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--text-2);
}
/* ── Audit controls ── */
/* Audit controls */
.audit-controls {
display: flex;
align-items: center;
@ -2126,7 +2126,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
background: var(--bg-2);
}
/* ── Clickable elements ── */
/* Clickable elements */
.clickable {
cursor: pointer;
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);
}
/* ── Progress bar ── */
/* Progress bar */
.progress-bar {
width: 100%;
height: 8px;
@ -2171,7 +2171,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
100% { transform: translateX(400%); }
}
/* ── Import status panel ── */
/* Import status panel */
.import-status-panel {
background: var(--bg-2);
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);
}
/* ── Sidebar import progress ── */
/* Sidebar import progress */
.sidebar-import-progress {
padding: 8px 12px;
background: var(--bg-2);
@ -2276,7 +2276,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
display: none;
}
/* ── Tag confirmation ── */
/* Tag confirmation */
.tag-confirm-delete {
display: inline-flex;
align-items: center;
@ -2305,7 +2305,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
text-decoration: underline;
}
/* ── Help overlay ── */
/* Help overlay */
.help-overlay {
position: fixed;
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);
}
/* ── Add media modal (collections) ── */
/* Add media modal (collections) */
.modal.wide {
max-width: 600px;
max-height: 70vh;
overflow-y: auto;
}
/* ── Database management ── */
/* Database management */
.db-actions {
display: flex;
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);
}
/* ── Library select-all banner ── */
/* Library select-all banner */
.select-all-banner {
display: flex;
align-items: center;
@ -2457,7 +2457,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
color: var(--text-0);
}
/* ── Media Player ── */
/* Media Player */
.media-player {
position: relative;
background: var(--bg-0);
@ -2610,7 +2610,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
border: none;
}
/* ── Image Viewer ── */
/* Image Viewer */
.image-viewer-overlay {
position: fixed;
inset: 0;
@ -2687,7 +2687,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
-webkit-user-drag: none;
}
/* ── Markdown Viewer ── */
/* Markdown Viewer */
.markdown-viewer {
padding: 16px;
text-align: left;
@ -2861,7 +2861,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 0.8em;
}
/* ── Frontmatter Card ── */
/* Frontmatter Card */
.frontmatter-card {
max-width: 800px;
background: var(--bg-2);
@ -2891,7 +2891,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin: 0;
}
/* ── Duplicates ── */
/* Duplicates */
.duplicates-view {
padding: 0;
}
@ -3058,7 +3058,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-weight: 600;
}
/* ── Login ── */
/* Login */
.login-container {
display: flex;
align-items: center;
@ -3113,7 +3113,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-top: 4px;
}
/* ── User Info (sidebar) ── */
/* User Info (sidebar) */
.user-info {
display: flex;
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-viewer { background: rgba(59, 120, 200, 0.1); color: #6ca0d4; }
/* ── Settings fields ── */
/* Settings fields */
.settings-field {
display: flex;
justify-content: space-between;
@ -3175,7 +3175,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
padding-top: 4px;
}
/* ── Detail no preview ── */
/* Detail no preview */
.detail-no-preview {
padding: 32px 16px;
text-align: center;
@ -3185,7 +3185,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
gap: 12px;
}
/* ── Light Theme ── */
/* Light Theme */
.theme-light {
--bg-0: #f5f5f7;
--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);
}
/* ── Skeleton Loading States ── */
/* Skeleton Loading States */
@keyframes skeleton-pulse {
0% { opacity: 0.6; }
50% { opacity: 0.3; }
@ -3307,7 +3307,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-size: 0.9rem;
}
/* ── Breadcrumb ── */
/* Breadcrumb */
.breadcrumb {
display: flex;
align-items: center;
@ -3337,7 +3337,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
font-weight: 500;
}
/* ── Queue Panel ── */
/* Queue Panel */
.queue-panel {
display: flex;
flex-direction: column;
@ -4135,7 +4135,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
margin-top: 8px;
}
/* ── Wikilink Styles (in markdown) ── */
/* Wikilink Styles (in markdown) */
.wikilink {
color: var(--accent-text);
text-decoration: none;
@ -4160,7 +4160,7 @@ ul li { padding: 3px 0; font-size: 12px; color: var(--text-1); }
cursor: default;
}
/* ── Light theme adjustments for links and graph ── */
/* Light theme adjustments for links and graph */
.theme-light .graph-nodes .graph-node text {
fill: var(--text-0);
}