diff --git a/src/interfaces/api/handlers/grant_handler.rs b/src/interfaces/api/handlers/grant_handler.rs
index 77ab00ee..bdd49756 100644
--- a/src/interfaces/api/handlers/grant_handler.rs
+++ b/src/interfaces/api/handlers/grant_handler.rs
@@ -676,12 +676,13 @@ pub async fn list_my_shares(
file_idx += 1;
match result {
Ok(file_dto) => {
+ // Caller is the granter — they had share-access to the
+ // resource, so the containing hierarchy is already known
+ // to them. Keep `path` (unlike list_shared_with_me).
items.push(OutgoingResourceItemDto {
resource_type: ResourceTypeDto::File,
first_shared_at: summary.first_shared_at,
- resource: ResourceContentDto::File(
- file_dto.clone().without_hierarchy_info(),
- ),
+ resource: ResourceContentDto::File(file_dto.clone()),
grants,
});
}
@@ -708,9 +709,7 @@ pub async fn list_my_shares(
items.push(OutgoingResourceItemDto {
resource_type: ResourceTypeDto::Folder,
first_shared_at: summary.first_shared_at,
- resource: ResourceContentDto::Folder(
- folder_dto.clone().without_hierarchy_info(),
- ),
+ resource: ResourceContentDto::Folder(folder_dto.clone()),
grants,
});
}
diff --git a/static/admin.html b/static/admin.html
index fa4bb92b..bc82c574 100644
--- a/static/admin.html
+++ b/static/admin.html
@@ -3,6 +3,10 @@
+
+
OxiCloud — Admin Panel
diff --git a/static/css/base/variables.css b/static/css/base/variables.css
index cb953e83..78496da7 100644
--- a/static/css/base/variables.css
+++ b/static/css/base/variables.css
@@ -1,49 +1,72 @@
+/*
+ * OxiCloud design tokens.
+ *
+ * Single source of truth for light + dark colours. Each token whose value
+ * differs between modes uses `light-dark(LIGHT, DARK)`, which the browser
+ * resolves against the page's `color-scheme`.
+ *
+ * Mode switching:
+ * • `` in declares
+ * both schemes are supported.
+ * • `:root { color-scheme: light dark }` (default) lets the UA follow the
+ * OS preference (`prefers-color-scheme`).
+ * • `html[data-color-scheme="light"]` / `…="dark"` force a specific mode.
+ * `theme-init.js` sets the attribute from localStorage.
+ *
+ * Fallback: browsers that don't support `light-dark()` (Chrome < 123 /
+ * Safari < 17.5 / Firefox < 120) hit a `@supports not (...)` block in
+ * `themes/dark.css` that still applies the old `[data-theme="dark"]` overrides.
+ */
+
:root {
+ /* Default: follow the OS preference. Overridden by html[data-color-scheme]. */
+ color-scheme: light dark;
+
--sidebar-width: 250px;
/* Backgrounds */
- --color-bg-page: #f5f7fa;
- --color-bg-surface: #ffffff;
- --color-bg-input: #f9fafb;
- --color-bg-hover: #f8fafc;
- --color-bg-muted: #f0f3f7;
- --color-bg-subtle: #f8f9fa;
- --color-bg-alt: #f7fafc;
- --color-bg-input-alt: #edf2f7;
- --color-bg-empty: #f0f0f0;
+ --color-bg-page: light-dark(#f5f7fa, #0f172a);
+ --color-bg-surface: light-dark(#ffffff, #1e293b);
+ --color-bg-input: light-dark(#f9fafb, #0f172a);
+ --color-bg-hover: light-dark(#f8fafc, #334155);
+ --color-bg-muted: light-dark(#f0f3f7, #1a2540);
+ --color-bg-subtle: light-dark(#f8f9fa, #162032);
+ --color-bg-alt: light-dark(#f7fafc, #0f172a);
+ --color-bg-input-alt: light-dark(#edf2f7, #253045);
+ --color-bg-empty: light-dark(#f0f0f0, #253045);
/* Borders */
- --color-border: #e2e8f0;
- --color-border-light: #f1f5f9;
- --color-border-medium: #cbd5e0;
- --color-border-faint: #e0e6ed;
- --color-border-subtle: #e0e5e8;
- --color-border-xfaint: #f0f0f0;
- --color-border-ddd: #ddd;
+ --color-border: light-dark(#e2e8f0, #334155);
+ --color-border-light: light-dark(#f1f5f9, #334155);
+ --color-border-medium: light-dark(#cbd5e0, #475569);
+ --color-border-faint: light-dark(#e0e6ed, #2a3650);
+ --color-border-subtle: light-dark(#e0e5e8, #2a3650);
+ --color-border-xfaint: light-dark(#f0f0f0, #1e293b);
+ --color-border-ddd: light-dark(#ddd, #334155);
/* Text */
- --color-text: #2d3748;
- --color-text-heading: #1e293b;
- --color-text-muted: #718096;
- --color-text-faint: #94a3b8;
- --color-text-secondary: #4a5568;
- --color-text-subtle: #64748b;
- --color-text-dark: #334155;
- --color-text-placeholder: #a0aec0;
- --color-text-gray: #6c757d;
- --color-text-medium: #666;
- --color-text-faint2: #888;
- --color-text-light: #999;
- --color-text-black: #333;
- --color-text-dim: #555;
+ --color-text: light-dark(#2d3748, #e2e8f0);
+ --color-text-heading: light-dark(#1e293b, #f1f5f9);
+ --color-text-muted: light-dark(#718096, #94a3b8);
+ --color-text-faint: light-dark(#94a3b8, #7a8a9f);
+ --color-text-secondary: light-dark(#4a5568, #cbd5e1);
+ --color-text-subtle: light-dark(#64748b, #94a3b8);
+ --color-text-dark: light-dark(#334155, #cbd5e1);
+ --color-text-placeholder: light-dark(#a0aec0, #64748b);
+ --color-text-gray: light-dark(#6c757d, #94a3b8);
+ --color-text-medium: light-dark(#666, #94a3b8);
+ --color-text-faint2: light-dark(#888, #64748b);
+ --color-text-light: light-dark(#999, #64748b);
+ --color-text-black: light-dark(#333, #e2e8f0);
+ --color-text-dim: light-dark(#555, #94a3b8);
- /* Accent */
+ /* Accent (orange) — mostly mode-agnostic. */
--color-accent: #ff5e3a;
- --color-accent-hover: #e04520;
+ --color-accent-hover: light-dark(#e04520, #ff7a5c);
--color-accent-gradient: linear-gradient(135deg, #ff5e3a 0%, #ff2d55 100%);
--color-accent-shadow: rgba(255, 94, 58, 0.3);
- --color-accent-ring: rgba(255, 94, 58, 0.1);
- --color-accent-tint: #fff5f3;
+ --color-accent-ring: light-dark(rgba(255, 94, 58, 0.1), rgba(255, 94, 58, 0.15));
+ --color-accent-tint: light-dark(#fff5f3, #2a1a15);
--color-accent-mid: #ff8a5c;
--color-accent-shadow-lg: rgba(255, 94, 58, 0.4);
--color-accent-bg: rgba(255, 94, 58, 0.06);
@@ -54,15 +77,15 @@
--color-accent-ring-xs: rgba(255, 94, 58, 0.05);
/* Feedback */
- --color-error-bg: #fee2e2;
- --color-error-text: #b91c1c;
- --color-success-bg: #dcfce7;
- --color-success-text: #15803d;
+ --color-error-bg: light-dark(#fee2e2, #3b1111);
+ --color-error-text: light-dark(#b91c1c, #fca5a5);
+ --color-success-bg: light-dark(#dcfce7, #052e16);
+ --color-success-text: light-dark(#15803d, #86efac);
--color-success-border: #48bb78;
--color-success-alt: #34c759;
- --color-success-bg-alt: #e6f4ea;
+ --color-success-bg-alt: light-dark(#e6f4ea, #0a2015);
--color-success-text-alt: #0d904f;
- --color-success-bg-green: #e8f5e9;
+ --color-success-bg-green: light-dark(#e8f5e9, #0a2015);
--color-success-text-green: #4eaa25;
/* Dangerous actions */
@@ -72,48 +95,48 @@
--color-danger-alt: #ef4444;
--color-danger-ring: rgba(239, 68, 68, 0.3);
--color-danger-ring-lg: rgba(239, 68, 68, 0.4);
- --color-danger-light-bg: #fef2f2;
- --color-danger-lighter: #fef2f2;
+ --color-danger-light-bg: light-dark(#fef2f2, #2a0c0c);
+ --color-danger-lighter: light-dark(#fef2f2, #2a0c0c);
--color-danger-text-alt: #e53e3e;
--color-danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
/* Warning/feedback */
- --color-warning-bg: #ffeaa7;
- --color-warning-bg-dark: #fdcb6e;
+ --color-warning-bg: light-dark(#ffeaa7, #3d2e00);
+ --color-warning-bg-dark: light-dark(#fdcb6e, #5a4200);
--color-warning-text: #ffc107;
--color-warning-border: #ffc107;
--color-warning-ring: rgba(255, 193, 7, 0.1);
--color-warning-shadow: rgba(255, 193, 7, 0.5);
- --color-warning-orange-bg: #fff3e0;
+ --color-warning-orange-bg: light-dark(#fff3e0, #2a1c10);
--color-warning-orange-border: #f57c00;
- --color-warning-orange-text: #f57c00;
- --color-warning-bg-light: #fef3c7;
- --color-warning-text-amber: #f59e0b;
+ --color-warning-orange-text: light-dark(#f57c00, #fb923c);
+ --color-warning-bg-light: light-dark(#fef3c7, #2a2410);
+ --color-warning-text-amber: light-dark(#f59e0b, #fbbf24);
--color-warning-bg-orange: #fef3e2;
--color-warning-text-orange: #d04423;
/* Info */
- --color-info-bg: #ebf5fb;
+ --color-info-bg: light-dark(#ebf5fb, #0c1e35);
--color-info-border: #2b6cb0;
--color-info-text: #2b6cb0;
- --color-info-bg-alt: #e0ecff;
+ --color-info-bg-alt: light-dark(#e0ecff, #0c1e35);
--color-info-text-alt: #3171d8;
- --color-info-surface: #e0e7ff;
+ --color-info-surface: light-dark(#e0e7ff, #0c1e35);
--color-info-blue: #3b82f6;
- /* Shadows */
- --color-shadow: rgba(0, 0, 0, 0.1);
- --color-shadow-lg: rgba(0, 0, 0, 0.12);
+ /* Shadows — slightly stronger in dark mode for parity. */
+ --color-shadow: light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3));
+ --color-shadow-lg: light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.3));
--color-shadow-xs: rgba(0, 0, 0, 0.05);
--color-shadow-sm: rgba(0, 0, 0, 0.08);
- --color-shadow-md: rgba(0, 0, 0, 0.15);
+ --color-shadow-md: light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.3));
--color-shadow-xl: rgba(0, 0, 0, 0.2);
--color-shadow-2xl: rgba(0, 0, 0, 0.25);
--color-shadow-3xl: rgba(0, 0, 0, 0.3);
--color-shadow-4xl: rgba(0, 0, 0, 0.4);
--color-shadow-5xl: rgba(0, 0, 0, 0.5);
- /* Overlays */
+ /* Overlays — same in both modes; they sit on top of arbitrary content. */
--color-overlay: rgba(0, 0, 0, 0.5);
--color-overlay-light: rgba(0, 0, 0, 0.45);
--color-overlay-heavy: rgba(0, 0, 0, 0.85);
@@ -129,15 +152,15 @@
/* Items */
--color-item: var(--color-bg-surface);
--color-item-hover: var(--color-bg-hover);
- --color-item-active: #f8d2ae;
- --color-item-selected: #fff8f6;
- --color-item-hover-accent: #fff0ec;
+ --color-item-active: light-dark(#f8d2ae, #5a5047);
+ --color-item-selected: light-dark(#fff8f6, #39281a);
+ --color-item-hover-accent: light-dark(#fff0ec, #3d342c);
--color-item-hover-blue: #f0f8ff;
--color-item-hover-sky: #e0f2fe;
- /* Sidebar — always dark */
- --color-sidebar-bg-from: #2a3042;
- --color-sidebar-bg-to: #232838;
+ /* Sidebar — already dark-leaning in both modes; dark mode goes deeper. */
+ --color-sidebar-bg-from: light-dark(#2a3042, #0f172a);
+ --color-sidebar-bg-to: light-dark(#232838, #0c1322);
--color-sidebar-text: rgba(255, 255, 255, 0.65);
--color-sidebar-text-hover: rgba(255, 255, 255, 0.9);
--color-sidebar-text-active: #ffffff;
@@ -236,7 +259,7 @@
--color-multiselect-danger-text-active: #ffffff;
/* Notification */
- --color-notification-bg: #ffffff;
+ --color-notification-bg: light-dark(#ffffff, #1e293b);
--color-notification-badge: #ff3b30;
--color-notification-success: #34c759;
--color-notification-error: #ff3b30;
@@ -252,7 +275,7 @@
--color-lightbox-text-muted: rgba(255, 255, 255, 0.7);
/* Purple/special */
- --color-purple-bg: #f3e8ff;
+ --color-purple-bg: light-dark(#f3e8ff, #1a0a33);
--color-purple-border: #7c3aed;
--color-purple-text: #7c3aed;
--color-purple-bg-gradient: linear-gradient(135deg, #e0e7ff 0%, #ede9fe 50%, #fce7f3 100%);
@@ -275,14 +298,14 @@
/* Content area */
--color-content-muted: #888;
- --color-content-bg-warn: #ffeaa7;
- --color-content-bg-warn-dark: #fdcb6e;
+ --color-content-bg-warn: light-dark(#ffeaa7, #3d2e00);
+ --color-content-bg-warn-dark: light-dark(#fdcb6e, #5a4200);
--color-content-debug-bg: red;
--color-content-debug-text: white;
/* User menu */
- --color-user-menu-header-bg: linear-gradient(135deg, #fef5f3 0%, #fdf2f8 100%);
- --color-user-menu-header-border: #fce7e1;
+ --color-user-menu-header-bg: light-dark(linear-gradient(135deg, #fef5f3 0%, #fdf2f8 100%), linear-gradient(135deg, #1a2332 0%, #1e2940 100%));
+ --color-user-menu-header-border: light-dark(#fce7e1, #3a2520);
/* Share dialog */
--color-share-link-text: #1565c0;
@@ -291,8 +314,8 @@
--color-share-owner-text: #757575;
/* Primary (style.css) */
- --color-primary: #2563eb;
- --color-primary-hover: #1d4ed8;
+ --color-primary: light-dark(#2563eb, #60a5fa);
+ --color-primary-hover: light-dark(#1d4ed8, #6fa8ee);
/* Recent view */
--color-recent-muted: #6c757d;
@@ -367,13 +390,13 @@
--color-storage-fill-orange: linear-gradient(90deg, #d97706, #f59e0b);
--color-storage-fill-red: linear-gradient(90deg, #dc2626, #ef4444);
- /* Error text (dark shade) */
- --color-error-text-dark: #991b1b;
+ /* Error text (dark shade) — light-mode is dark red, dark-mode is light red. */
+ --color-error-text-dark: light-dark(#991b1b, #f87171);
/* Stat warning border */
--color-stat-warn-border: #fbbf24;
- /* Status badge — light mode (success/emerald) */
+ /* Status badge — success/emerald */
--color-badge-success-bg: #ecfdf5;
--color-badge-success-bg-medium: #d1fae5;
--color-badge-success-text: #065f46;
@@ -384,32 +407,33 @@
--color-badge-green-bg: #ecfdf5;
--color-badge-green-text: #065f46;
- /* Status badge — light mode (orange/coral) */
- --color-badge-orange-bg: #fff5f3;
- --color-badge-orange-text: #ff5e3a;
+ /* Status badge — orange/coral (used by role-chip & user-vignette) */
+ --color-badge-orange-bg: light-dark(#fff5f3, #2a1814);
+ --color-badge-orange-text: light-dark(#ff5e3a, #ff8a65);
- /* Status badge — light mode (error/red) */
+ /* Status badge — error/red */
--color-badge-error-border: #fecaca;
- /* Status badge — light mode (warning/amber) */
+ /* Status badge — warning/amber */
--color-badge-warning-text: #92400e;
--color-badge-warning-border: #fde68a;
--color-badge-amber-bg: #fef3c7;
--color-badge-amber-text: #f59e0b;
- /* Status badge — light mode (indigo/purple) */
+ /* Status badge — indigo/purple */
--color-badge-indigo-bg: #ede9fe;
--color-badge-indigo-text: #6d28d9;
- /* Status badge — light mode (blue) */
- --color-badge-blue-bg: #eff6ff;
- --color-badge-blue-text: #1e40af;
+ /* Status badge — blue (used by role-chip & user-vignette) */
+ --color-badge-blue-bg: light-dark(#eff6ff, #0c2d48);
+ --color-badge-blue-text: light-dark(#1e40af, #93c5fd);
--color-badge-blue-border: #bfdbfe;
- /* Status badge — light mode (gray/disabled) */
+ /* Status badge — gray/disabled */
--color-badge-gray: #d1d5db;
- /* FIXME: Status badge — dark mode */
+ /* FIXME: legacy dark-mode badge tokens — kept for now; can be folded into
+ * the main badges once their last consumers migrate. */
--color-badge-success-dark-bg: #052e16;
--color-badge-success-dark-border: #065f46;
--color-badge-error-dark-bg: #3b1111;
@@ -439,3 +463,13 @@
--color-video-play: white;
--color-video-play-shadow: black;
}
+
+/* Explicit user choice overrides the OS preference. `theme-init.js` writes
+ * the attribute from localStorage on render-blocking startup. */
+html[data-color-scheme="light"] {
+ color-scheme: light;
+}
+
+html[data-color-scheme="dark"] {
+ color-scheme: dark;
+}
diff --git a/static/css/components/itemTooltip.css b/static/css/components/itemTooltip.css
index eaf3b5d8..1248d773 100644
--- a/static/css/components/itemTooltip.css
+++ b/static/css/components/itemTooltip.css
@@ -1,29 +1,19 @@
/* ── Item tooltip — "technical sheet" ─────────────────────────────────────── */
/* */
-/* Overlays the search bar area in the top-bar while hovering a file item. */
-/* Spans from the sidebar right-edge to just before .user-controls. */
+/* Absolutely overlays `.search-slot` in the top-bar. The search bar stays in */
+/* place underneath; only the tooltip's opacity is animated, so there is no */
+/* second element to keep in sync and no display:none flicker between rapid */
+/* hover transitions. RTL just works (inset:0). */
/* */
-/* 3-column CSS grid: [icon] [label] [value] */
-/* All values start at the same x position regardless of label width. */
+/* 3-column CSS grid: [icon] [label] [value] */
+/* All values start at the same x position regardless of label width. */
.path-tooltip {
- position: fixed;
-
- /* Fill most of the top-bar (topbar height 70px, 8px margin top/bottom) */
- top: 8px;
-
- /* Desktop: starts at sidebar right edge + top-bar padding */
- left: calc(var(--sidebar-width) + 30px);
-
- /*
- * Right edge stops just before .user-controls:
- * 30px (top-bar right padding) + ~90px (notif bell + gap + avatar btn) + 4px gap
- * Using left + right instead of width so the browser computes it dynamically.
- */
- right: 124px;
-
- /* High enough to sit above the top-bar content */
- z-index: 200;
+ /* Cover the search slot, sit above it (later in DOM order). */
+ position: absolute;
+ inset: 0;
+ /* Opaque background so the search bar underneath is fully masked. */
+ background-color: var(--color-bg-surface);
display: grid;
grid-template-columns: 1em max-content 1fr;
@@ -31,20 +21,24 @@
row-gap: 6px;
align-items: center;
- /* Fill the top-bar height minus an 8px margin on each side */
- height: 54px;
- padding: 0 16px;
- border-radius: 10px;
- border: 2px solid var(--color-border-medium);
-
- background-color: var(--color-bg-input);
color: var(--color-text-muted);
font-size: 0.75rem;
pointer-events: none;
- /* avoid blinking when pointer moves between items */
- transition: display 0.2s allow-discrete;
+ /* Show instantly. The hide direction (.hidden) overrides this with a
+ 1s delay so the tooltip lingers after the mouse leaves an item;
+ rapid item-to-item moves cancel the pending fade before it fires. */
+ opacity: 1;
+ transition: opacity 0.2s ease 0s;
+}
+
+/* Hidden state — override the global `.hidden { display:none !important }`
+ so we can fade with opacity instead of snapping to display:none. */
+.path-tooltip.hidden {
+ display: grid !important;
+ opacity: 0;
+ transition-delay: 1s;
}
/* Icon column */
@@ -85,23 +79,6 @@
font-style: italic;
}
-/* ── Narrow screens (sidebar hidden) ────────────────────────────────────── */
-/* On mobile the sidebar slides off-screen; align after the #sidebar-toggle */
-/* button (≈44px) that takes its place in the top-bar. */
-
-@media (max-width: 768px) {
- .path-tooltip {
- /* 16px topbar padding + ~44px sidebar-toggle + 8px gap */
- left: 68px;
-
- /*
- * Right edge stops before search-toggle-btn + user-controls:
- * ~40px (search-toggle) + 12px (gap) + ~90px (user-controls) + 16px (padding)
- */
- right: 158px;
- }
-}
-
/* ── Vignette inside tooltip ─────────────────────────────────────────────── */
/* The vignette name hard-codes 14px; scale it down to match the tooltip. */
diff --git a/static/css/components/userMenu.css b/static/css/components/userMenu.css
index 1a7ede6f..68f559c6 100644
--- a/static/css/components/userMenu.css
+++ b/static/css/components/userMenu.css
@@ -182,34 +182,58 @@
color: var(--color-text-subtle);
}
-.user-menu-item .theme-toggle-pill {
+/* The appearance row is a plain container, not a button — the inner
+ * segmented control captures the clicks. Match the height of other
+ * .user-menu-item rows so the row reads as part of the same list. */
+.user-menu-item--theme {
+ cursor: default;
+}
+
+.user-menu-item--theme:hover {
+ background: transparent;
+}
+
+/* Light / Like OS / Dark — three-option pill, active option highlighted
+ * with the accent colour. Sits at the right edge of the row. */
+.theme-segmented {
margin-left: auto;
- width: 40px;
+ display: inline-flex;
+ background: var(--color-bg-muted);
+ border: 1px solid var(--color-border);
+ border-radius: 999px;
+ padding: 2px;
+ gap: 2px;
+}
+
+.theme-segmented__opt {
+ width: 28px;
height: 22px;
- background: var(--color-border);
- border-radius: 11px;
- position: relative;
- transition: background 0.3s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ background: transparent;
+ color: var(--color-text-subtle);
+ border-radius: 999px;
+ cursor: pointer;
+ font-size: 11px;
+ padding: 0;
+ transition:
+ background 0.15s ease,
+ color 0.15s ease;
}
-.user-menu-item .theme-toggle-pill.active {
+.theme-segmented__opt:hover {
+ color: var(--color-text);
+}
+
+.theme-segmented__opt--active {
background: var(--color-accent);
+ color: var(--color-danger-text);
}
-.user-menu-item .theme-toggle-knob {
- width: 18px;
- height: 18px;
- background: var(--color-danger-text);
- border-radius: 50%;
- position: absolute;
- top: 2px;
- left: 2px;
- transition: transform 0.3s ease;
- box-shadow: 0 1px 4px var(--color-shadow-md);
-}
-
-.user-menu-item .theme-toggle-pill.active .theme-toggle-knob {
- transform: translateX(18px);
+.theme-segmented__opt--active:hover {
+ color: var(--color-danger-text);
}
.user-menu-admin {
diff --git a/static/css/layout/topbar.css b/static/css/layout/topbar.css
index d0a392ea..d0f27eda 100644
--- a/static/css/layout/topbar.css
+++ b/static/css/layout/topbar.css
@@ -86,11 +86,14 @@
color: var(--color-text-heading);
}
-.search-container {
+/* Slot wrapping .search-container + #path-tooltip — it owns the flex slot
+ so the tooltip can absolutely overlay the search bar via inset:0 instead
+ of viewport-fixed positioning. */
+.search-slot {
flex-grow: 1;
max-width: 600px;
- position: relative;
margin-right: 20px;
+ position: relative;
display: flex;
align-items: center;
@@ -100,6 +103,13 @@
}
}
+.search-container {
+ flex-grow: 1;
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
.search-container input {
width: 100%;
padding: 12px 50px 12px 44px;
@@ -204,8 +214,8 @@
justify-content: center;
}
- /* Hide full search bar on mobile by default */
- .search-container {
+ /* Hide full search slot on mobile by default */
+ .search-slot {
display: none;
}
@@ -224,7 +234,7 @@
justify-content: center;
}
- .top-bar--search-active .search-container {
+ .top-bar--search-active .search-slot {
display: flex;
flex-grow: 1;
max-width: none;
diff --git a/static/css/themes/dark.css b/static/css/themes/dark.css
index 5f7b1df3..98b49b8d 100644
--- a/static/css/themes/dark.css
+++ b/static/css/themes/dark.css
@@ -1,122 +1,151 @@
-[data-theme="dark"] {
- /* Backgrounds */
- --color-bg-page: #0f172a;
- --color-bg-surface: #1e293b;
- --color-bg-input: #0f172a;
- --color-bg-hover: #334155;
- --color-bg-muted: #1a2540;
- --color-bg-subtle: #162032;
- --color-bg-alt: #0f172a;
- --color-bg-input-alt: #253045;
- --color-bg-empty: #253045;
+/*
+ * Legacy dark-mode fallback for browsers that don't support `light-dark()`
+ * (Chrome < 123 / Safari < 17.5 / Firefox < 120, all from early 2024).
+ *
+ * Modern browsers skip this entire stylesheet at parse time thanks to the
+ * `@supports not (color: light-dark(white, black))` gate — the merged tokens
+ * in `base/variables.css` handle dark mode declaratively via the
+ * `color-scheme` CSS property.
+ *
+ * On capable browsers this file becomes dead code; once the supported
+ * baseline raises beyond the cut-off, it can be deleted.
+ *
+ * The selector matches BOTH attribute names so the fallback works during
+ * the transitional window (when some users may still have `data-theme`
+ * stamped from cached app state):
+ * • [data-theme="dark"] — legacy attribute, never set by new code
+ * • html[data-color-scheme="dark"] — new attribute set by theme-init.js
+ */
- /* Borders */
- --color-border: #334155;
- --color-border-light: #334155;
- --color-border-medium: #475569;
- --color-border-faint: #2a3650;
- --color-border-subtle: #2a3650;
- --color-border-xfaint: #1e293b;
- --color-border-ddd: #334155;
+@supports not (color: light-dark(white, black)) {
+ [data-theme="dark"],
+ html[data-color-scheme="dark"] {
+ /* Backgrounds */
+ --color-bg-page: #0f172a;
+ --color-bg-surface: #1e293b;
+ --color-bg-input: #0f172a;
+ --color-bg-hover: #334155;
+ --color-bg-muted: #1a2540;
+ --color-bg-subtle: #162032;
+ --color-bg-alt: #0f172a;
+ --color-bg-input-alt: #253045;
+ --color-bg-empty: #253045;
- /* Text */
- --color-text: #e2e8f0;
- --color-text-heading: #f1f5f9;
- --color-text-muted: #94a3b8;
- --color-text-faint: #7a8a9f;
- --color-text-secondary: #cbd5e1;
- --color-text-subtle: #94a3b8;
- --color-text-dark: #cbd5e1;
- --color-text-placeholder: #64748b;
- --color-text-gray: #94a3b8;
- --color-text-medium: #94a3b8;
- --color-text-faint2: #64748b;
- --color-text-light: #64748b;
- --color-text-black: #e2e8f0;
- --color-text-dim: #94a3b8;
+ /* Borders */
+ --color-border: #334155;
+ --color-border-light: #334155;
+ --color-border-medium: #475569;
+ --color-border-faint: #2a3650;
+ --color-border-subtle: #2a3650;
+ --color-border-xfaint: #1e293b;
+ --color-border-ddd: #334155;
- /* Accent */
- --color-accent: #ff5e3a;
- --color-accent-hover: #ff7a5c;
- --color-accent-gradient: linear-gradient(135deg, #ff5e3a 0%, #ff2d55 100%);
- --color-accent-shadow: rgba(255, 94, 58, 0.3);
- --color-accent-ring: rgba(255, 94, 58, 0.15);
- --color-accent-tint: #2a1a15;
+ /* Text */
+ --color-text: #e2e8f0;
+ --color-text-heading: #f1f5f9;
+ --color-text-muted: #94a3b8;
+ --color-text-faint: #7a8a9f;
+ --color-text-secondary: #cbd5e1;
+ --color-text-subtle: #94a3b8;
+ --color-text-dark: #cbd5e1;
+ --color-text-placeholder: #64748b;
+ --color-text-gray: #94a3b8;
+ --color-text-medium: #94a3b8;
+ --color-text-faint2: #64748b;
+ --color-text-light: #64748b;
+ --color-text-black: #e2e8f0;
+ --color-text-dim: #94a3b8;
- /* Feedback */
- --color-error-bg: #3b1111;
- --color-error-text: #fca5a5;
- --color-success-bg: #052e16;
- --color-success-text: #86efac;
+ /* Accent */
+ --color-accent: #ff5e3a;
+ --color-accent-hover: #ff7a5c;
+ --color-accent-gradient: linear-gradient(135deg, #ff5e3a 0%, #ff2d55 100%);
+ --color-accent-shadow: rgba(255, 94, 58, 0.3);
+ --color-accent-ring: rgba(255, 94, 58, 0.15);
+ --color-accent-tint: #2a1a15;
- /* Shadows (stronger on dark mode) */
- --color-shadow: rgba(0, 0, 0, 0.3);
- --color-shadow-lg: rgba(0, 0, 0, 0.3);
- --color-shadow-md: rgba(0, 0, 0, 0.3);
+ /* Feedback */
+ --color-error-bg: #3b1111;
+ --color-error-text: #fca5a5;
+ --color-success-bg: #052e16;
+ --color-success-text: #86efac;
- /* Additional dark overrides */
- --color-warning-bg: #3d2e00;
- --color-warning-bg-dark: #5a4200;
- --color-notification-bg: #1e293b;
- --color-user-menu-header-bg: linear-gradient(135deg, #1a2332 0%, #1e2940 100%);
- --color-user-menu-header-border: #3a2520;
- --color-info-bg: #0c1e35;
- --color-info-bg-alt: #0c1e35;
- --color-info-surface: #0c1e35;
- --color-danger-light-bg: #2a0c0c;
- --color-danger-lighter: #2a0c0c;
- --color-error-text-dark: #f87171;
+ /* Shadows (stronger on dark mode) */
+ --color-shadow: rgba(0, 0, 0, 0.3);
+ --color-shadow-lg: rgba(0, 0, 0, 0.3);
+ --color-shadow-md: rgba(0, 0, 0, 0.3);
- /* Pastel chip backgrounds need dark equivalents — otherwise the light
- * cream / white-blue / pink-cream backdrops glow against dark surfaces
- * and the saturated text colours become unreadable on the now-dark fill.
- * Same pattern: dark tinted background + lighter pastel text. */
- --color-badge-orange-bg: #2a1814;
- --color-badge-orange-text: #ff8a65;
- --color-badge-blue-bg: #0c2d48;
- --color-badge-blue-text: #93c5fd;
- --color-warning-bg-light: #2a2410;
- --color-warning-text-amber: #fbbf24;
- --color-warning-orange-bg: #2a1c10;
- --color-warning-orange-text: #fb923c;
- --color-purple-bg: #1a0a33;
- --color-success-bg-alt: #0a2015;
- --color-success-bg-green: #0a2015;
- --color-content-bg-warn: #3d2e00;
- --color-content-bg-warn-dark: #5a4200;
+ /* Additional dark overrides */
+ --color-warning-bg: #3d2e00;
+ --color-warning-bg-dark: #5a4200;
+ --color-notification-bg: #1e293b;
+ --color-user-menu-header-bg: linear-gradient(135deg, #1a2332 0%, #1e2940 100%);
+ --color-user-menu-header-border: #3a2520;
+ --color-info-bg: #0c1e35;
+ --color-info-bg-alt: #0c1e35;
+ --color-info-surface: #0c1e35;
+ --color-danger-light-bg: #2a0c0c;
+ --color-danger-lighter: #2a0c0c;
+ --color-error-text-dark: #f87171;
- /* Sidebar */
- --color-sidebar-bg-from: #0f172a;
- --color-sidebar-bg-to: #0c1322;
+ /* Pastel chip backgrounds need dark equivalents — otherwise the light
+ * cream / white-blue / pink-cream backdrops glow against dark surfaces
+ * and the saturated text colours become unreadable on the now-dark fill.
+ * Same pattern: dark tinted background + lighter pastel text. */
+ --color-badge-orange-bg: #2a1814;
+ --color-badge-orange-text: #ff8a65;
+ --color-badge-blue-bg: #0c2d48;
+ --color-badge-blue-text: #93c5fd;
+ --color-warning-bg-light: #2a2410;
+ --color-warning-text-amber: #fbbf24;
+ --color-warning-orange-bg: #2a1c10;
+ --color-warning-orange-text: #fb923c;
+ --color-purple-bg: #1a0a33;
+ --color-success-bg-alt: #0a2015;
+ --color-success-bg-green: #0a2015;
+ --color-content-bg-warn: #3d2e00;
+ --color-content-bg-warn-dark: #5a4200;
- /* Items */
- --color-item-active: #5a5047;
- --color-item-selected: #39281a;
- --color-item-hover-accent: #3d342c;
+ /* Sidebar */
+ --color-sidebar-bg-from: #0f172a;
+ --color-sidebar-bg-to: #0c1322;
- /* Primary */
- /* TODO: rename into primary-blue ? */
- --color-primary: #60a5fa;
- --color-primary-hover: #6fa8ee;
-}
-
-/* ------------ */
-
-[data-theme="dark"] select {
- background-color: #1e293b;
- border-color: #334155;
- color: #e2e8f0;
-}
-
-[data-theme="dark"] select:hover {
- border-color: #475569;
-}
-
-[data-theme="dark"] select:focus {
- border-color: #ff5e3a;
- background-color: #1e293b;
-}
-
-[data-theme="dark"] .smd-expiry-date-input::-webkit-calendar-picker-indicator {
- filter: invert(1);
+ /* Items */
+ --color-item-active: #5a5047;
+ --color-item-selected: #39281a;
+ --color-item-hover-accent: #3d342c;
+
+ /* Primary */
+ /* TODO: rename into primary-blue ? */
+ --color-primary: #60a5fa;
+ --color-primary-hover: #6fa8ee;
+ }
+
+ /* ------------ */
+ /* Native form controls need explicit dark styling on browsers without
+ * `color-scheme` — on capable browsers the UA does this automatically
+ * once `color-scheme: dark` is in effect. */
+
+ [data-theme="dark"] select,
+ html[data-color-scheme="dark"] select {
+ background-color: #1e293b;
+ border-color: #334155;
+ color: #e2e8f0;
+ }
+
+ [data-theme="dark"] select:hover,
+ html[data-color-scheme="dark"] select:hover {
+ border-color: #475569;
+ }
+
+ [data-theme="dark"] select:focus,
+ html[data-color-scheme="dark"] select:focus {
+ border-color: #ff5e3a;
+ background-color: #1e293b;
+ }
+
+ [data-theme="dark"] .smd-expiry-date-input::-webkit-calendar-picker-indicator,
+ html[data-color-scheme="dark"] .smd-expiry-date-input::-webkit-calendar-picker-indicator {
+ filter: invert(1);
+ }
}
diff --git a/static/index.html b/static/index.html
index 9a74a6e9..08a3a772 100644
--- a/static/index.html
+++ b/static/index.html
@@ -3,6 +3,10 @@
+
+
OxiCloud
@@ -132,12 +136,17 @@
-