feat(light-dark): normalize light/dark/like-os mode

permits user to define light, dark or like the desktop
This commit is contained in:
Edouard Vanbelle
2026-05-30 01:06:32 +02:00
parent ea83891a61
commit ca1a2bb649
13 changed files with 494 additions and 342 deletions
+4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
<meta name="color-scheme" content="light dark" />
<title>OxiCloud — Admin Panel</title>
<link rel="icon" type="image/svg+xml" href="/logo/logo-plain.svg">
<script src="/js/core/theme-init.js"></script>
+118 -84
View File
@@ -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:
* • `<meta name="color-scheme" content="light dark">` in <head> 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;
}
+45 -21
View File
@@ -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 {
+140 -111
View File
@@ -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);
}
}
+25 -4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
<meta name="color-scheme" content="light dark">
<title data-i18n="app.title">OxiCloud</title>
<link rel="icon" type="image/svg+xml" href="/logo/logo-plain.svg">
<!-- Apply saved theme immediately to prevent flash of light mode -->
@@ -195,13 +199,30 @@
</button>
<div class="user-menu-divider hidden" id="user-menu-admin-divider"></div>
<div id="language-selector"></div>
<button class="user-menu-item" id="user-menu-theme">
<div class="user-menu-item user-menu-item--theme">
<i class="fas fa-moon"></i>
<span data-i18n="user_menu.appearance">Appearance</span>
<div class="theme-toggle-pill" id="theme-toggle-pill">
<div class="theme-toggle-knob"></div>
<div class="theme-segmented" id="user-menu-theme-segmented" role="radiogroup" data-i18n-aria-label="user_menu.appearance" aria-label="Appearance">
<button type="button" class="theme-segmented__opt" data-mode="light"
role="radio" aria-checked="false"
data-i18n-title="user_menu.theme.light" title="Light"
data-i18n-aria-label="user_menu.theme.light" aria-label="Light">
<i class="fas fa-sun"></i>
</button>
<button type="button" class="theme-segmented__opt" data-mode="auto"
role="radio" aria-checked="false"
data-i18n-title="user_menu.theme.auto" title="Like OS"
data-i18n-aria-label="user_menu.theme.auto" aria-label="Like OS">
<i class="fas fa-desktop"></i>
</button>
<button type="button" class="theme-segmented__opt" data-mode="dark"
role="radio" aria-checked="false"
data-i18n-title="user_menu.theme.dark" title="Dark"
data-i18n-aria-label="user_menu.theme.dark" aria-label="Dark">
<i class="fas fa-moon"></i>
</button>
</div>
</button>
</div>
<button class="user-menu-item" id="user-menu-about">
<i class="fas fa-info-circle"></i>
<span data-i18n="user_menu.about">About OxiCloud</span>
+38 -43
View File
@@ -6,14 +6,13 @@ import { createUserVignette } from '../components/userVignette.js';
import { getCsrfHeaders } from '../core/csrf.js';
import { formatFileSize, formatQuotaSize } from '../core/formatters.js';
import { i18n } from '../core/i18n.js';
import { ui } from './ui.js';
function setupUserMenu() {
const wrapper = document.getElementById('user-menu-wrapper');
const avatarBtn = document.getElementById('user-avatar-btn');
const menu = document.getElementById('user-menu');
const logoutBtn = document.getElementById('user-menu-logout');
const themeBtn = document.getElementById('user-menu-theme');
const themeSegmented = document.getElementById('user-menu-theme-segmented');
const aboutBtn = document.getElementById('user-menu-about');
const adminBtn = document.getElementById('user-menu-admin');
const adminDivider = document.getElementById('user-menu-admin-divider');
@@ -65,53 +64,49 @@ function setupUserMenu() {
});
}
if (themeBtn) {
const pill = document.getElementById('theme-toggle-pill');
// Sync pill UI with current theme state
function syncThemePill() {
const isDark = localStorage.getItem('oxicloud_theme') === 'dark';
if (pill) {
if (isDark) {
pill.classList.add('active');
} else {
pill.classList.remove('active');
}
}
// Ensure document theme matches localStorage
if (isDark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
if (themeSegmented) {
/**
* Read the current selection. Returns `'auto'` when no explicit choice
* is stored — that's the default and means "follow OS preference".
* @returns {'light' | 'dark' | 'auto'}
*/
function currentMode() {
const saved = localStorage.getItem('oxicloud_theme');
return saved === 'light' || saved === 'dark' ? saved : 'auto';
}
// Initialize pill state on load
syncThemePill();
themeBtn.addEventListener('click', (e) => {
e.stopPropagation();
// Toggle theme based on current state, not pill state
const currentIsDark = localStorage.getItem('oxicloud_theme') === 'dark';
const newIsDark = !currentIsDark;
localStorage.setItem('oxicloud_theme', newIsDark ? 'dark' : 'light');
if (newIsDark) {
document.documentElement.setAttribute('data-theme', 'dark');
/**
* Apply a mode: persist it, stamp the html attribute (or remove it
* for 'auto' so CSS `color-scheme: light dark` resolves via OS), and
* highlight the matching button.
* @param {'light' | 'dark' | 'auto'} mode
*/
function applyMode(mode) {
if (mode === 'auto') {
localStorage.setItem('oxicloud_theme', 'auto');
document.documentElement.removeAttribute('data-color-scheme');
} else {
document.documentElement.removeAttribute('data-theme');
localStorage.setItem('oxicloud_theme', mode);
document.documentElement.setAttribute('data-color-scheme', mode);
}
themeSegmented.querySelectorAll('.theme-segmented__opt').forEach((b) => {
const el = /** @type {HTMLElement} */ (b);
const isActive = el.dataset.mode === mode;
el.classList.toggle('theme-segmented__opt--active', isActive);
el.setAttribute('aria-checked', String(isActive));
});
}
if (pill) {
if (newIsDark) {
pill.classList.add('active');
} else {
pill.classList.remove('active');
}
}
// Initial highlight (theme-init.js already stamped the attribute).
applyMode(currentMode());
ui.showNotification(newIsDark ? '🌙' : '☀️', newIsDark ? 'Dark mode enabled' : 'Light mode enabled');
themeSegmented.addEventListener('click', (e) => {
const btn = /** @type {HTMLElement | null} */ (/** @type {HTMLElement} */ (e.target).closest('.theme-segmented__opt'));
if (!btn) return;
e.stopPropagation();
const mode = /** @type {'light' | 'dark' | 'auto'} */ (btn.dataset.mode);
if (mode !== 'light' && mode !== 'dark' && mode !== 'auto') return;
applyMode(mode);
});
}
+75 -68
View File
@@ -17,18 +17,6 @@
/** @type {Record<String, Array<number | String>>} */
const OxiIcons = {
'arrow-left': [
448,
'M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z'
],
'arrows-alt': [
512,
'M278.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l32 0 0 96-96 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6l0-32 96 0 0 96-32 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8l-32 0 0-96 96 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 32-96 0 0-96 32 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-64-64z'
],
'arrow-up': [
512,
'M214.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 109.3 160 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z'
],
'arrow-down': [
512,
'M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z'
@@ -41,6 +29,22 @@ const OxiIcons = {
576,
'M246.6 374.6l-96 96c-12.5 12.5-32.8 12.5-45.3 0l-96-96c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L96 370.7 96 64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 306.7 41.4-41.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3zM320 480c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-160 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l224 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L320 96z'
],
'arrow-left': [
448,
'M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z'
],
'arrow-up': [
512,
'M214.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 109.3 160 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z'
],
'arrows-alt': [
512,
'M278.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l32 0 0 96-96 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6l0-32 96 0 0 96-32 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8l-32 0 0-96 96 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 32-96 0 0-96 32 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-64-64z'
],
backward: [
512,
'M204.3 43.1C215.9 32 233 28.9 247.7 35.2S272 56 272 72l0 136.3 172.3-165.1C455.9 32 473 28.9 487.7 35.2S512 56 512 72l0 368c0 16-9.6 30.5-24.3 36.8s-31.8 3.2-43.4-7.9L272 303.7 272 440c0 16-9.6 30.5-24.3 36.8s-31.8 3.2-43.4-7.9l-192-184C4.5 277.3 0 266.9 0 256s4.5-21.3 12.3-28.9l192-184z'
],
ban: [
512,
'M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM159.3 388.7L388.7 159.3c4.6-4.6 11.5-5.9 17.4-3.5c14.5 6 26.4 15.3 35.1 27c3.8 5.2 3.2 12.3-1.2 16.8L210.2 428.4c-4.4 4.4-11.6 5-16.8 1.2c-11.7-8.7-21-20.6-27-35.1c-2.5-5.9-1.1-12.8 3.5-17.4z'
@@ -69,6 +73,10 @@ const OxiIcons = {
512,
'M120 0c13.3 0 24 10.7 24 24l0 40 160 0 0-40c0-13.3 10.7-24 24-24s24 10.7 24 24l0 40 32 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 128C0 92.7 28.7 64 64 64l32 0 0-40c0-13.3 10.7-24 24-24zm0 112l-56 0c-8.8 0-16 7.2-16 16l0 48 352 0 0-48c0-8.8-7.2-16-16-16l-264 0zM48 224l0 192c0 8.8 7.2 16 16 16l320 0c8.8 0 16-7.2 16-16l0-192-352 0z'
],
camera: [
512,
'M149.1 64.8L138.7 96 64 96C28.7 96 0 124.7 0 160L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-256c0-35.3-28.7-64-64-64l-74.7 0-10.4-31.2C356.4 45.2 338.1 32 317.4 32L194.6 32c-20.7 0-39 13.2-45.5 32.8zM256 192a96 96 0 1 1 0 192 96 96 0 1 1 0-192z'
],
'caret-down': [
320,
'M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z'
@@ -125,23 +133,30 @@ const OxiIcons = {
640,
'M308.5 135.3c7.1-6.3 9.9-16.2 6.2-25c-2.3-5.3-4.8-10.5-7.6-15.5L304 89.4c-3-5-6.3-9.9-9.8-14.6c-5.7-7.6-15.7-10.1-24.7-7.1l-28.2 9.3c-10.7-8.8-23-16-36.2-20.9L199 27.1c-1.9-9.3-9.1-16.7-18.5-17.8C173.9 8.4 167.2 8 160.4 8l-.7 0c-6.8 0-13.5 .4-20.1 1.2c-9.4 1.1-16.6 8.6-18.5 17.8L115 56.1c-13.3 5-25.5 12.1-36.2 20.9L50.5 67.8c-9-3-19-.5-24.7 7.1c-3.5 4.7-6.8 9.6-9.9 14.6l-3 5.3c-2.8 5-5.3 10.2-7.6 15.6c-3.7 8.7-.9 18.6 6.2 25l22.2 19.8C32.6 161.9 32 168.9 32 176s.6 14.1 1.7 20.9L11.5 216.7c-7.1 6.3-9.9 16.2-6.2 25c2.3 5.3 4.8 10.5 7.6 15.6l3 5.2c3 5.1 6.3 9.9 9.9 14.6c5.7 7.6 15.7 10.1 24.7 7.1l28.2-9.3c10.7 8.8 23 16 36.2 20.9l6.1 29.1c1.9 9.3 9.1 16.7 18.5 17.8c6.7 .8 13.5 1.2 20.4 1.2s13.7-.4 20.4-1.2c9.4-1.1 16.6-8.6 18.5-17.8l6.1-29.1c13.3-5 25.5-12.1 36.2-20.9l28.2 9.3c9 3 19 .5 24.7-7.1c3.5-4.7 6.8-9.5 9.8-14.6l3.1-5.4c2.8-5 5.3-10.2 7.6-15.5c3.7-8.7 .9-18.6-6.2-25l-22.2-19.8c1.1-6.8 1.7-13.8 1.7-20.9s-.6-14.1-1.7-20.9l22.2-19.8zM112 176a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zM504.7 500.5c6.3 7.1 16.2 9.9 25 6.2c5.3-2.3 10.5-4.8 15.5-7.6l5.4-3.1c5-3 9.9-6.3 14.6-9.8c7.6-5.7 10.1-15.7 7.1-24.7l-9.3-28.2c8.8-10.7 16-23 20.9-36.2l29.1-6.1c9.3-1.9 16.7-9.1 17.8-18.5c.8-6.7 1.2-13.5 1.2-20.4s-.4-13.7-1.2-20.4c-1.1-9.4-8.6-16.6-17.8-18.5L583.9 307c-5-13.3-12.1-25.5-20.9-36.2l9.3-28.2c3-9 .5-19-7.1-24.7c-4.7-3.5-9.6-6.8-14.6-9.9l-5.3-3c-5-2.8-10.2-5.3-15.6-7.6c-8.7-3.7-18.6-.9-25 6.2l-19.8 22.2c-6.8-1.1-13.8-1.7-20.9-1.7s-14.1 .6-20.9 1.7l-19.8-22.2c-6.3-7.1-16.2-9.9-25-6.2c-5.3 2.3-10.5 4.8-15.6 7.6l-5.2 3c-5.1 3-9.9 6.3-14.6 9.9c-7.6 5.7-10.1 15.7-7.1 24.7l9.3 28.2c-8.8 10.7-16 23-20.9 36.2L315.1 313c-9.3 1.9-16.7 9.1-17.8 18.5c-.8 6.7-1.2 13.5-1.2 20.4s.4 13.7 1.2 20.4c1.1 9.4 8.6 16.6 17.8 18.5l29.1 6.1c5 13.3 12.1 25.5 20.9 36.2l-9.3 28.2c-3 9-.5 19 7.1 24.7c4.7 3.5 9.5 6.8 14.6 9.8l5.4 3.1c5 2.8 10.2 5.3 15.5 7.6c8.7 3.7 18.6 .9 25-6.2l19.8-22.2c6.8 1.1 13.8 1.7 20.9 1.7s14.1-.6 20.9-1.7l19.8 22.2zM464 304a48 48 0 1 1 0 96 48 48 0 1 1 0-96z'
],
crown: [
576,
'M309 106c11.4-7 19-19.7 19-34c0-22.1-17.9-40-40-40s-40 17.9-40 40c0 14.4 7.6 27 19 34L209.7 220.6c-9.1 18.2-32.7 23.4-48.6 10.7L72 160c5-6.7 8-15 8-24c0-22.1-17.9-40-40-40S0 113.9 0 136s17.9 40 40 40c.2 0 .5 0 .7 0L86.4 427.4c5.5 30.4 32 52.6 63 52.6l277.2 0c30.9 0 57.4-22.1 63-52.6L535.3 176c.2 0 .5 0 .7 0c22.1 0 40-17.9 40-40s-17.9-40-40-40s-40 17.9-40 40c0 9 3 17.3 8 24l-89.1 71.3c-15.9 12.7-39.5 7.5-48.6-10.7L309 106z'
'compact-disc': [
512,
'M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zM88 256H56c0-105.9 86.1-192 192-192v32c-88.2 0-160 71.8-160 160zm160 96c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96zm0-128c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z'
],
copy: [
448,
'M208 0L332.1 0c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9L448 336c0 26.5-21.5 48-48 48l-192 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48zM48 128l80 0 0 64-64 0 0 256 192 0 0-32 64 0 0 48c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 176c0-26.5 21.5-48 48-48z'
],
crown: [
576,
'M309 106c11.4-7 19-19.7 19-34c0-22.1-17.9-40-40-40s-40 17.9-40 40c0 14.4 7.6 27 19 34L209.7 220.6c-9.1 18.2-32.7 23.4-48.6 10.7L72 160c5-6.7 8-15 8-24c0-22.1-17.9-40-40-40S0 113.9 0 136s17.9 40 40 40c.2 0 .5 0 .7 0L86.4 427.4c5.5 30.4 32 52.6 63 52.6l277.2 0c30.9 0 57.4-22.1 63-52.6L535.3 176c.2 0 .5 0 .7 0c22.1 0 40-17.9 40-40s-17.9-40-40-40s-40 17.9-40 40c0 9 3 17.3 8 24l-89.1 71.3c-15.9 12.7-39.5 7.5-48.6-10.7L309 106z'
],
database: [
448,
'M448 80l0 48c0 44.2-100.3 80-224 80S0 172.2 0 128L0 80C0 35.8 100.3 0 224 0S448 35.8 448 80zM393.2 214.7c20.8-7.4 39.9-16.9 54.8-28.6L448 288c0 44.2-100.3 80-224 80S0 332.2 0 288L0 186.1c14.9 11.8 34 21.2 54.8 28.6C99.7 230.7 159.5 240 224 240s124.3-9.3 169.2-25.3zM0 346.1c14.9 11.8 34 21.2 54.8 28.6C99.7 390.7 159.5 400 224 400s124.3-9.3 169.2-25.3c20.8-7.4 39.9-16.9 54.8-28.6l0 85.9c0 44.2-100.3 80-224 80S0 476.2 0 432l0-85.9z'
],
desktop: [
576,
'M64 0C28.7 0 0 28.7 0 64L0 352c0 35.3 28.7 64 64 64l176 0-10.7 32L160 448c-17.7 0-32 14.3-32 32s14.3 32 32 32l256 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-69.3 0L336 416l176 0c35.3 0 64-28.7 64-64l0-288c0-35.3-28.7-64-64-64L64 0zM512 64l0 224L64 288 64 64l448 0z'
],
download: [
512,
'M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 242.7-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7 288 32zM64 352c-35.3 0-64 28.7-64 64l0 32c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-32c0-35.3-28.7-64-64-64l-101.5 0-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352 64 352zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'
],
'ellipsis-v': [128, 'M64 360a56 56 0 1 0 0 112 56 56 0 1 0 0-112zm0-160a56 56 0 1 0 0 112 56 56 0 1 0 0-112zM120 96A56 56 0 1 0 8 96a56 56 0 1 0 112 0z'],
envelope: [
512,
'M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48L48 64zM0 176L0 384c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-208L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z'
@@ -158,6 +173,10 @@ const OxiIcons = {
448,
'M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'
],
'external-link-alt': [
512,
'M290.4 19.8C295.4 7.8 307.1 0 320 0L480 0c17.7 0 32 14.3 32 32l0 160c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9L400 157.3 246.6 310.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L354.7 112 297.4 54.6c-9.2-9.2-11.9-22.9-6.9-34.9zM0 176c0-44.2 35.8-80 80-80l80 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-80 0c-8.8 0-16 7.2-16 16l0 256c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-80c0-17.7 14.3-32 32-32s32 14.3 32 32l0 80c0 44.2-35.8 80-80 80L80 512c-44.2 0-80-35.8-80-80L0 176z'
],
eye: [
576,
'M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z'
@@ -218,6 +237,10 @@ const OxiIcons = {
512,
'M512 416c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l128 0c20.1 0 39.1 9.5 51.2 25.6l19.2 25.6c6 8.1 15.5 12.8 25.6 12.8l160 0c35.3 0 64 28.7 64 64l0 256zM232 376c0 13.3 10.7 24 24 24s24-10.7 24-24l0-64 64 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-64 0 0-64c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 64-64 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l64 0 0 64z'
],
forward: [
512,
'M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z'
],
github: [
496,
'M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z'
@@ -226,6 +249,10 @@ const OxiIcons = {
512,
'M352 256c0 22.2-1.2 43.6-3.3 64l-185.3 0c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64l185.3 0c2.2 20.4 3.3 41.8 3.3 64zm28.8-64l123.1 0c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64l-123.1 0c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32l-116.7 0c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0l-176.6 0c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0L18.6 160C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192l123.1 0c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64L8.1 320C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6l176.6 0c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352l116.7 0zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6l116.7 0z'
],
'grip-vertical': [
320,
'M128 40c0-22.1-17.9-40-40-40L40 0C17.9 0 0 17.9 0 40L0 88c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zm0 192c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM0 424l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 40c0-22.1-17.9-40-40-40L232 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM192 232l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 424c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48z'
],
hdd: [
512,
'M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 184.4c-17-15.2-39.4-24.4-64-24.4L64 256c-24.6 0-47 9.2-64 24.4L0 96zM64 288l384 0c35.3 0 64 28.7 64 64l0 64c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64zM320 416a32 32 0 1 0 0-64 32 32 0 1 0 0 64zm128-32a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z'
@@ -262,6 +289,10 @@ const OxiIcons = {
512,
'M232.5 5.2c14.9-6.9 32.1-6.9 47 0l218.6 101c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L13.9 149.8C5.4 145.8 0 137.3 0 128s5.4-17.9 13.9-21.8L232.5 5.2zM48.1 218.4l164.3 75.9c27.7 12.8 59.6 12.8 87.3 0l164.3-75.9 34.1 15.8c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L13.9 277.8C5.4 273.8 0 265.3 0 256s5.4-17.9 13.9-21.8l34.1-15.8zM13.9 362.2l34.1-15.8 164.3 75.9c27.7 12.8 59.6 12.8 87.3 0l164.3-75.9 34.1 15.8c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L13.9 405.8C5.4 401.8 0 393.3 0 384s5.4-17.9 13.9-21.8z'
],
'level-up-alt': [
384,
'M169.4 9.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9S332.9 192 320 192l-64 0 0 160c0 88.4-71.6 160-160 160l-64 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l64 0c17.7 0 32-14.3 32-32l0-160-64 0c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128z'
],
link: [
576,
'M419.5 96c-16.6 0-32.7 4.5-46.8 12.7-15.8-16-34.2-29.4-54.5-39.5 28.2-24 64.1-37.2 101.3-37.2 86.4 0 156.5 70 156.5 156.5 0 41.5-16.5 81.3-45.8 110.6l-71.1 71.1c-29.3 29.3-69.1 45.8-110.6 45.8-86.4 0-156.5-70-156.5-156.5 0-1.5 0-3 .1-4.5 .5-17.7 15.2-31.6 32.9-31.1s31.6 15.2 31.1 32.9c0 .9 0 1.8 0 2.6 0 51.1 41.4 92.5 92.5 92.5 24.5 0 48-9.7 65.4-27.1l71.1-71.1c17.3-17.3 27.1-40.9 27.1-65.4 0-51.1-41.4-92.5-92.5-92.5zM275.2 173.3c-1.9-.8-3.8-1.9-5.5-3.1-12.6-6.5-27-10.2-42.1-10.2-24.5 0-48 9.7-65.4 27.1L91.1 258.2c-17.3 17.3-27.1 40.9-27.1 65.4 0 51.1 41.4 92.5 92.5 92.5 16.5 0 32.6-4.4 46.7-12.6 15.8 16 34.2 29.4 54.6 39.5-28.2 23.9-64 37.2-101.3 37.2-86.4 0-156.5-70-156.5-156.5 0-41.5 16.5-81.3 45.8-110.6l71.1-71.1c29.3-29.3 69.1-45.8 110.6-45.8 86.6 0 156.5 70.6 156.5 156.9 0 1.3 0 2.6 0 3.9-.4 17.7-15.1 31.6-32.8 31.2s-31.6-15.1-31.2-32.8c0-.8 0-1.5 0-2.3 0-33.7-18-63.3-44.8-79.6z'
@@ -282,6 +313,10 @@ const OxiIcons = {
384,
'M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z'
],
music: [
512,
'M468 7c7.6 6.1 12 15.3 12 25l0 304c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6l0-116.7-224 49.8 0 206.3c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6L128 96c0-15 10.4-28 25.1-31.2l288-64c9.5-2.1 19.4 .2 27 6.3z'
],
oxiexport: [
576,
'M384.5 24l0 72-64 0c-79.5 0-144 64.5-144 144 0 93.4 82.8 134.8 100.6 142.6 2.2 1 4.6 1.4 7.1 1.4l2.5 0c9.8 0 17.8-8 17.8-17.8 0-8.3-5.9-15.5-12.8-20.3-8.9-6.2-19.2-18.2-19.2-40.5 0-45 36.5-81.5 81.5-81.5l30.5 0 0 72c0 9.7 5.8 18.5 14.8 22.2s19.3 1.7 26.2-5.2l136-136c9.4-9.4 9.4-24.6 0-33.9L425.5 7c-6.9-6.9-17.2-8.9-26.2-5.2S384.5 14.3 384.5 24zm-272 72c-44.2 0-80 35.8-80 80l0 256c0 44.2 35.8 80 80 80l256 0c44.2 0 80-35.8 80-80l0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 32c0 8.8-7.2 16-16 16l-256 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l16 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-16 0z'
@@ -290,50 +325,34 @@ const OxiIcons = {
576,
'm 360.55,24 v 72 h 64 c 79.5,0 144,64.5 144,144 0,93.4 -82.8,134.8 -100.6,142.6 -2.2,1 -4.6,1.4 -7.1,1.4 h -2.5 c -9.8,0 -17.8,-8 -17.8,-17.8 0,-8.3 5.9,-15.5 12.8,-20.3 8.9,-6.2 19.2,-18.2 19.2,-40.5 0,-45 -36.5,-81.5 -81.5,-81.5 h -30.5 v 72 c 0,9.7 -5.8,18.5 -14.8,22.2 -9,3.7 -19.3,1.7 -26.2,-5.2 l -136,-136 c -9.4,-9.4 -9.4,-24.6 0,-33.9 l 136,-136 c 6.9,-6.9 17.2,-8.9 26.2,-5.2 9,3.7 14.8,12.5 14.8,22.2 z M 112.5,96 c -44.2,0 -80,35.8 -80,80 v 256 c 0,44.2 35.8,80 80,80 h 256 c 44.2,0 80,-35.8 80,-80 v -32 c 0,-17.7 -14.3,-32 -32,-32 -17.7,0 -32,14.3 -32,32 v 32 c 0,8.8 -7.2,16 -16,16 h -256 c -8.8,0 -16,-7.2 -16,-16 V 176 c 0,-8.8 7.2,-16 16,-16 h 16 c 17.7,0 32,-14.3 32,-32 0,-17.7 -14.3,-32 -32,-32 z'
],
pen: [
512,
'M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z'
],
play: [
384,
'M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80L0 432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z'
],
pause: [
384,
'M48 32C21.5 32 0 53.5 0 80L0 432c0 26.5 21.5 48 48 48l64 0c26.5 0 48-21.5 48-48l0-352c0-26.5-21.5-48-48-48L48 32zm224 0c-26.5 0-48 21.5-48 48l0 352c0 26.5 21.5 48 48 48l64 0c26.5 0 48-21.5 48-48l0-352c0-26.5-21.5-48-48-48l-64 0z'
],
pen: [
512,
'M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z'
],
'pencil-alt': [
512,
'M36.4 353.2c4.1-14.6 11.8-27.9 22.6-38.7l181.2-181.2 33.9-33.9c16.6 16.6 51.3 51.3 104 104l33.9 33.9-33.9 33.9-181.2 181.2c-10.7 10.7-24.1 18.5-38.7 22.6L30.4 510.6c-8.3 2.3-17.3 0-23.4-6.2S-1.4 489.3 .9 481L36.4 353.2zm55.6-3.7c-4.4 4.7-7.6 10.4-9.3 16.6l-24.1 86.9 86.9-24.1c6.4-1.8 12.2-5.1 17-9.7L91.9 349.5zm354-146.1c-16.6-16.6-51.3-51.3-104-104L308 65.5C334.5 39 349.4 24.1 352.9 20.6 366.4 7 384.8-.6 404-.6S441.6 7 455.1 20.6l35.7 35.7C504.4 69.9 512 88.3 512 107.4s-7.6 37.6-21.2 51.1c-3.5 3.5-18.4 18.4-44.9 44.9z'
],
shuffle: [
512,
'M403.8 34.4c12-5 25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64c-9.2 9.2-22.9 11.9-34.9 6.9S384 204.9 384 192l0-32-32 0c-10.1 0-19.6 4.7-25.6 12.8l-32.4 43.2-40-53.3 21.2-28.3C293.3 110.2 321.8 96 352 96l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6zM154 296l40 53.3-21.2 28.3C154.7 401.8 126.2 416 96 416l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c10.1 0 19.6-4.7 25.6-12.8L154 296zM438.6 470.6c-9.2 9.2-22.9 11.9-34.9 6.9S384 460.9 384 448l0-32-32 0c-30.2 0-58.7-14.2-76.8-38.4L121.6 172.8c-6-8.1-15.5-12.8-25.6-12.8l-64 0c-17.7 0-32-14.3-32-32S14.3 96 32 96l64 0c30.2 0 58.7 14.2 76.8 38.4L326.4 339.2c6 8.1 15.5 12.8 25.6 12.8l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6s25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64z'
play: [
384,
'M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80L0 432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z'
],
repeat: [
512,
'M470.6 118.6c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S352 19.1 352 32l0 32-160 0C86 64 0 150 0 256 0 273.7 14.3 288 32 288s32-14.3 32-32c0-70.7 57.3-128 128-128l160 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64zM41.4 393.4c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9S160 492.9 160 480l0-32 160 0c106 0 192-86 192-192 0-17.7-14.3-32-32-32s-32 14.3-32 32c0 70.7-57.3 128-128 128l-160 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64z'
],
forward: [
512,
'M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z'
],
backward: [
512,
'M204.3 43.1C215.9 32 233 28.9 247.7 35.2S272 56 272 72l0 136.3 172.3-165.1C455.9 32 473 28.9 487.7 35.2S512 56 512 72l0 368c0 16-9.6 30.5-24.3 36.8s-31.8 3.2-43.4-7.9L272 303.7 272 440c0 16-9.6 30.5-24.3 36.8s-31.8 3.2-43.4-7.9l-192-184C4.5 277.3 0 266.9 0 256s4.5-21.3 12.3-28.9l192-184z'
],
volume: [
512,
'M48 352l48 0 134.1 119.2c6.4 5.7 14.6 8.8 23.1 8.8 19.2 0 34.8-15.6 34.8-34.8l0-378.4c0-19.2-15.6-34.8-34.8-34.8-8.5 0-16.7 3.1-23.1 8.8L96 160 48 160c-26.5 0-48 21.5-48 48l0 96c0 26.5 21.5 48 48 48zM441.1 107c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C443.3 170.7 464 210.9 464 256s-20.7 85.3-53.2 111.8c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5c43.2-35.2 70.9-88.9 70.9-149s-27.7-113.8-70.9-149zm-60.5 74.5c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C361.1 227.6 368 241 368 256s-6.9 28.4-17.7 37.3c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5C402.1 312.9 416 286.1 416 256s-13.9-56.9-35.5-74.5z'
],
'compact-disc': [
512,
'M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zM88 256H56c0-105.9 86.1-192 192-192v32c-88.2 0-160 71.8-160 160zm160 96c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96zm0-128c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z'
plus: [
448,
'M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z'
],
'question-circle': [
512,
'M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM169.8 165.3c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z'
],
repeat: [
512,
'M470.6 118.6c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S352 19.1 352 32l0 32-160 0C86 64 0 150 0 256 0 273.7 14.3 288 32 288s32-14.3 32-32c0-70.7 57.3-128 128-128l160 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64zM41.4 393.4c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9S160 492.9 160 480l0-32 160 0c106 0 192-86 192-192 0-17.7-14.3-32-32-32s-32 14.3-32 32c0 70.7-57.3 128-128 128l-160 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64z'
],
save: [
448,
'M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-242.7c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32L64 32zm0 96c0-17.7 14.3-32 32-32l192 0c17.7 0 32 14.3 32 32l0 64c0 17.7-14.3 32-32 32L96 224c-17.7 0-32-14.3-32-32l0-64zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z'
@@ -362,6 +381,10 @@ const OxiIcons = {
512,
'M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8l0 378.1C394 378 431.1 230.1 432 141.4L256 66.8s0 0 0 0z'
],
shuffle: [
512,
'M403.8 34.4c12-5 25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64c-9.2 9.2-22.9 11.9-34.9 6.9S384 204.9 384 192l0-32-32 0c-10.1 0-19.6 4.7-25.6 12.8l-32.4 43.2-40-53.3 21.2-28.3C293.3 110.2 321.8 96 352 96l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6zM154 296l40 53.3-21.2 28.3C154.7 401.8 126.2 416 96 416l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c10.1 0 19.6-4.7 25.6-12.8L154 296zM438.6 470.6c-9.2 9.2-22.9 11.9-34.9 6.9S384 460.9 384 448l0-32-32 0c-30.2 0-58.7-14.2-76.8-38.4L121.6 172.8c-6-8.1-15.5-12.8-25.6-12.8l-64 0c-17.7 0-32-14.3-32-32S14.3 96 32 96l64 0c30.2 0 58.7 14.2 76.8 38.4L326.4 339.2c6 8.1 15.5 12.8 25.6 12.8l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6s25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64z'
],
'sign-in-alt': [
512,
'M217.9 105.9L340.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L217.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1L32 320c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM352 416l64 0c17.7 0 32-14.3 32-32l0-256c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c53 0 96 43 96 96l0 256c0 53-43 96-96 96l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z'
@@ -386,6 +409,10 @@ const OxiIcons = {
576,
'M287.9 0c9.2 0 17.6 5.2 21.6 13.5l68.6 141.3 153.2 22.6c9 1.3 16.5 7.6 19.3 16.3s.5 18.1-5.9 24.5L439.6 319.9l24.6 145.7c1.5 9-2.2 18.1-9.7 23.5s-17.3 6-25.3 1.7l-137-73.2L155.2 490.8c-8 4.3-17.8 3.7-25.3-1.7s-11.2-14.5-9.7-23.5l24.6-145.7L39.6 218.2c-6.4-6.4-8.7-15.9-5.9-24.5s10.3-14.9 19.3-16.3l153.2-22.6L274.3 13.5C278.3 5.2 286.7 0 295.9 0h-8zm0 79L235.4 187.2c-3.5 7.1-10.2 12.1-18.1 13.3L99 218.9l85.8 85.1c5.5 5.5 8.1 13.3 6.8 21L171.3 444.7l111.5-59.5c7-3.7 15.3-3.7 22.3 0l111.5 59.5-20.3-119.7c-1.3-7.7 1.2-15.5 6.8-21l85.8-85.1-118.3-17.4c-7.8-1.2-14.6-6.1-18.1-13.3L287.9 79z'
],
sun: [
512,
'M375.7 19.7c-1.5-8-6.9-14.7-14.4-17.8s-16.1-2.2-22.8 2.4L256 61 173.5 4.2c-6.7-4.6-15.3-5.5-22.8-2.4s-12.9 9.8-14.4 17.8l-18.1 98.5L19.7 136.3c-8 1.5-14.7 6.9-17.8 14.4s-2.2 16.1 2.4 22.8L61 256 4.2 338.5c-4.6 6.7-5.5 15.3-2.4 22.8s9.8 13 17.8 14.4l98.5 18.1 18.1 98.5c1.5 8 6.9 14.7 14.4 17.8s16.1 2.2 22.8-2.4L256 451l82.5 56.8c6.7 4.6 15.3 5.5 22.8 2.4s12.9-9.8 14.4-17.8l18.1-98.5 98.5-18.1c8-1.5 14.7-6.9 17.8-14.4s2.2-16.1-2.4-22.8L451 256l56.8-82.5c4.6-6.7 5.5-15.3 2.4-22.8s-9.8-12.9-17.8-14.4l-98.5-18.1L375.7 19.7zM269.6 110l65.6-45.2 14.4 78.3c1.8 9.8 9.5 17.5 19.3 19.3l78.3 14.4L402 242.4c-5.7 8.2-5.7 19 0 27.2l45.2 65.6-78.3 14.4c-9.8 1.8-17.5 9.5-19.3 19.3l-14.4 78.3L269.6 402c-8.2-5.7-19-5.7-27.2 0l-65.6 45.2-14.4-78.3c-1.8-9.8-9.5-17.5-19.3-19.3L64.8 335.2 110 269.6c5.7-8.2 5.7-19 0-27.2L64.8 176.8l78.3-14.4c9.8-1.8 17.5-9.5 19.3-19.3l14.4-78.3L242.4 110c8.2 5.7 19 5.7 27.2 0zM256 368a112 112 0 1 0 0-224 112 112 0 1 0 0 224zM192 256a64 64 0 1 1 128 0 64 64 0 1 1 -128 0z'
],
terminal: [
576,
'M9.4 86.6C-3.1 74.1-3.1 53.9 9.4 41.4s32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L178.7 256 9.4 86.6zM256 416l288 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-288 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z'
@@ -438,29 +465,9 @@ const OxiIcons = {
512,
'M342.6 9.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l9.4 9.4L28.1 342.6C10.1 360.6 0 385 0 410.5L0 416c0 53 43 96 96 96l5.5 0c25.5 0 49.9-10.1 67.9-28.1L448 205.3l9.4 9.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-32-32-96-96-32-32zM205.3 256L352 109.3 402.7 160l-96 96-101.5 0z'
],
camera: [
volume: [
512,
'M149.1 64.8L138.7 96 64 96C28.7 96 0 124.7 0 160L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-256c0-35.3-28.7-64-64-64l-74.7 0-10.4-31.2C356.4 45.2 338.1 32 317.4 32L194.6 32c-20.7 0-39 13.2-45.5 32.8zM256 192a96 96 0 1 1 0 192 96 96 0 1 1 0-192z'
],
'external-link-alt': [
512,
'M290.4 19.8C295.4 7.8 307.1 0 320 0L480 0c17.7 0 32 14.3 32 32l0 160c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9L400 157.3 246.6 310.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L354.7 112 297.4 54.6c-9.2-9.2-11.9-22.9-6.9-34.9zM0 176c0-44.2 35.8-80 80-80l80 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-80 0c-8.8 0-16 7.2-16 16l0 256c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-80c0-17.7 14.3-32 32-32s32 14.3 32 32l0 80c0 44.2-35.8 80-80 80L80 512c-44.2 0-80-35.8-80-80L0 176z'
],
'grip-vertical': [
320,
'M128 40c0-22.1-17.9-40-40-40L40 0C17.9 0 0 17.9 0 40L0 88c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zm0 192c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM0 424l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 40c0-22.1-17.9-40-40-40L232 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM192 232l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 424c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48z'
],
'level-up-alt': [
384,
'M169.4 9.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9S332.9 192 320 192l-64 0 0 160c0 88.4-71.6 160-160 160l-64 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l64 0c17.7 0 32-14.3 32-32l0-160-64 0c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128z'
],
music: [
512,
'M468 7c7.6 6.1 12 15.3 12 25l0 304c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6l0-116.7-224 49.8 0 206.3c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6L128 96c0-15 10.4-28 25.1-31.2l288-64c9.5-2.1 19.4 .2 27 6.3z'
],
plus: [
448,
'M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z'
'M48 352l48 0 134.1 119.2c6.4 5.7 14.6 8.8 23.1 8.8 19.2 0 34.8-15.6 34.8-34.8l0-378.4c0-19.2-15.6-34.8-34.8-34.8-8.5 0-16.7 3.1-23.1 8.8L96 160 48 160c-26.5 0-48 21.5-48 48l0 96c0 26.5 21.5 48 48 48zM441.1 107c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C443.3 170.7 464 210.9 464 256s-20.7 85.3-53.2 111.8c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5c43.2-35.2 70.9-88.9 70.9-149s-27.7-113.8-70.9-149zm-60.5 74.5c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C361.1 227.6 368 241 368 256s-6.9 28.4-17.7 37.3c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5C402.1 312.9 416 286.1 416 256s-13.9-56.9-35.5-74.5z'
],
'volume-up': [
640,
+21 -3
View File
@@ -1,3 +1,21 @@
// Apply saved theme immediately (render-blocking) to prevent FOUC.
// This file MUST be loaded without "defer" or "async".
if (localStorage.getItem('oxicloud_theme') === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
// Apply saved colour-scheme choice immediately (render-blocking) to prevent FOUC.
// This file MUST be loaded WITHOUT "defer" or "async" so it runs before any paint.
//
// localStorage values:
// 'light' / 'dark' → force that mode (stamp html[data-color-scheme]).
// 'auto' / absent → follow the OS preference (no attribute → CSS default
// `color-scheme: light dark` resolves via prefers-color-scheme).
//
// Backward compat: existing users have 'light' / 'dark' stored from the old
// binary toggle; both still apply correctly. New 'auto' value is introduced
// by the segmented control in user-menu.
(() => {
var saved = localStorage.getItem('oxicloud_theme');
var html = document.documentElement;
if (saved === 'light' || saved === 'dark') {
html.setAttribute('data-color-scheme', saved);
} else {
html.removeAttribute('data-color-scheme');
}
})();
+6 -1
View File
@@ -126,7 +126,12 @@
"about_description": "Cloud storage platform built with Rust & Clean Architecture. Fast, secure, and private.",
"admin_panel": "Admin Panel",
"profile": "My Profile",
"role_user": "User"
"role_user": "User",
"theme": {
"light": "Light",
"dark": "Dark",
"auto": "Like OS"
}
},
"share": {
"dialogTitle": "Share Link",
+4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
<meta name="color-scheme" content="light dark">
<title data-i18n="app.title">OxiCloud - Login</title>
<link rel="icon" type="image/svg+xml" href="/logo/logo-plain.svg">
<!-- Apply saved theme immediately to prevent flash of light mode -->
+4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
<meta name="color-scheme" content="light dark">
<title>OxiCloud — My Profile</title>
<link rel="icon" type="image/svg+xml" href="/logo/logo-plain.svg">
<script src="/js/core/theme-init.js"></script>
+4
View File
@@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
<meta name="color-scheme" content="light dark">
<title>OxiCloud — Shared</title>
<link rel="icon" type="image/svg+xml" href="/logo/logo-plain.svg">
<script src="/js/core/theme-init.js"></script>
+10 -7
View File
@@ -59,12 +59,15 @@ test.describe('authenticated as admin', () => {
test('theme can be changed to dark', async ({ page }) => {
await page.locator('#user-avatar-btn').click();
await expect(page.locator('#user-menu')).toBeVisible();
await page.locator('#theme-toggle-pill').click();
// html element must carry data-theme="dark"
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
// The appearance row is now a 3-option segmented control
// (Light / Like OS / Dark). Each option carries a `data-mode` attribute.
await page.locator('.theme-segmented__opt[data-mode="dark"]').click();
// localStorage must persist the choice
// html element must carry data-color-scheme="dark" (new attribute).
await expect(page.locator('html')).toHaveAttribute('data-color-scheme', 'dark');
// localStorage must persist the choice.
const theme = await page.evaluate(() => localStorage.getItem('oxicloud_theme'));
expect(theme).toBe('dark');
@@ -75,9 +78,9 @@ test.describe('authenticated as admin', () => {
mask: [page.locator('.storage-bar'), page.locator('.storage-info') ]
});
// Toggle back to light
await page.locator('#theme-toggle-pill').click();
await expect(page.locator('html')).not.toHaveAttribute('data-theme', 'dark');
// Switch back to light via the Light option.
await page.locator('.theme-segmented__opt[data-mode="light"]').click();
await expect(page.locator('html')).toHaveAttribute('data-color-scheme', 'light');
const themeAfter = await page.evaluate(() => localStorage.getItem('oxicloud_theme'));
expect(themeAfter).toBe('light');