feat: deliver the redesigned desktop workspace
Rebuild downloads, link collection, settings, history, and statistics around a responsive desktop shell with compact account and queue tables, contextual navigation, persistent update affordances, unified overlays, and accessible keyboard interactions. Add safe history-folder reveal IPC, responsive 2560/1920/1366/1120 coverage, deterministic visual fixtures, focused component regressions, and release-tree exclusions for internal working files. Bump the public application version to 2.0.13.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import { Icon } from "../ui/Icon";
|
||||
import { buildMainNavigation, type MainView } from "./shell-model";
|
||||
|
||||
export interface AppHeaderProps {
|
||||
activeView: MainView;
|
||||
onViewChange: (view: MainView) => void;
|
||||
actions: ReactNode;
|
||||
}
|
||||
|
||||
export function AppHeader({ activeView, onViewChange, actions }: AppHeaderProps): ReactElement {
|
||||
const navigation = buildMainNavigation(activeView);
|
||||
|
||||
return (
|
||||
<header className="md-shell-header" data-ui-region="header">
|
||||
<div className="md-shell-brand">Multi-Debrid-Downloader</div>
|
||||
<nav aria-label="Hauptnavigation" className="md-shell-navigation" role="tablist">
|
||||
{navigation.map((item) => (
|
||||
<button
|
||||
aria-current={item.active ? "page" : undefined}
|
||||
aria-selected={item.active}
|
||||
className={`md-shell-navigation-item${item.active ? " is-active" : ""}`}
|
||||
data-visual-active-view={item.active ? item.id : undefined}
|
||||
key={item.id}
|
||||
onClick={() => onViewChange(item.id)}
|
||||
role="tab"
|
||||
title={item.label}
|
||||
type="button"
|
||||
>
|
||||
<Icon name={item.icon} size={18} />
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
{actions ? <div aria-label="Globale Aktionen" className="md-shell-header-actions" role="group">{actions}</div> : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { useState, useSyncExternalStore, type ReactElement, type ReactNode } from "react";
|
||||
import { ContextInfoButton } from "../ui/ContextInfoButton";
|
||||
import { AppHeader } from "./AppHeader";
|
||||
import { AppSidebar } from "./AppSidebar";
|
||||
import {
|
||||
getMainViewLabel,
|
||||
getResponsiveShellMode,
|
||||
type MainView,
|
||||
type ResponsiveShellMode
|
||||
} from "./shell-model";
|
||||
import "./shell.css";
|
||||
|
||||
function subscribeToViewport(callback: () => void): () => void {
|
||||
if (typeof window === "undefined") {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
window.addEventListener("resize", callback);
|
||||
return () => window.removeEventListener("resize", callback);
|
||||
}
|
||||
|
||||
function getViewportShellMode(): ResponsiveShellMode {
|
||||
return typeof window === "undefined" ? "full" : getResponsiveShellMode(window.innerWidth);
|
||||
}
|
||||
|
||||
function getServerShellMode(): ResponsiveShellMode {
|
||||
return "full";
|
||||
}
|
||||
|
||||
export interface AppShellProps {
|
||||
activeView: MainView;
|
||||
onViewChange: (view: MainView) => void;
|
||||
sidebar: ReactNode;
|
||||
sidebarStatus: ReactNode;
|
||||
headerActions: ReactNode;
|
||||
toolbar: ReactNode;
|
||||
children: ReactNode;
|
||||
footer: ReactNode;
|
||||
contextInfo: ReactNode;
|
||||
sidebarCollapsed: boolean;
|
||||
onSidebarCollapsedChange: (collapsed: boolean) => void;
|
||||
}
|
||||
|
||||
export function AppShell({
|
||||
activeView,
|
||||
onViewChange,
|
||||
sidebar,
|
||||
sidebarStatus,
|
||||
headerActions,
|
||||
toolbar,
|
||||
children,
|
||||
footer,
|
||||
contextInfo,
|
||||
sidebarCollapsed,
|
||||
onSidebarCollapsedChange
|
||||
}: AppShellProps): ReactElement {
|
||||
const [infoOpen, setInfoOpen] = useState(false);
|
||||
const [responsiveSidebarExpanded, setResponsiveSidebarExpanded] = useState(false);
|
||||
const responsiveMode = useSyncExternalStore(
|
||||
subscribeToViewport,
|
||||
getViewportShellMode,
|
||||
getServerShellMode
|
||||
);
|
||||
const hasSidebar = Boolean(sidebar || sidebarStatus);
|
||||
const responsiveSidebarCollapsed = responsiveMode !== "full" && !responsiveSidebarExpanded;
|
||||
const effectiveSidebarCollapsed = sidebarCollapsed || responsiveSidebarCollapsed;
|
||||
|
||||
const setSidebarCollapsed = (collapsed: boolean): void => {
|
||||
if (responsiveMode !== "full") {
|
||||
setResponsiveSidebarExpanded(!collapsed);
|
||||
}
|
||||
onSidebarCollapsedChange(collapsed);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`md-shell is-${responsiveMode}${hasSidebar ? " has-sidebar" : ""}${hasSidebar && effectiveSidebarCollapsed ? " has-collapsed-sidebar" : ""}`}
|
||||
data-responsive-mode={responsiveMode}
|
||||
>
|
||||
<AppHeader activeView={activeView} actions={headerActions} onViewChange={onViewChange} />
|
||||
<div className="md-shell-workspace">
|
||||
{hasSidebar ? (
|
||||
<AppSidebar
|
||||
collapsed={effectiveSidebarCollapsed}
|
||||
onCollapsedChange={setSidebarCollapsed}
|
||||
responsiveRail={responsiveSidebarCollapsed}
|
||||
status={sidebarStatus}
|
||||
>
|
||||
{sidebar}
|
||||
</AppSidebar>
|
||||
) : null}
|
||||
<section className="md-shell-main" data-ui-region="main">
|
||||
{toolbar ? <div className="md-shell-toolbar" data-ui-region="toolbar">{toolbar}</div> : null}
|
||||
<div className="md-shell-content">{children}</div>
|
||||
{footer ? <div className="md-shell-footer" data-ui-region="footer">{footer}</div> : null}
|
||||
<ContextInfoButton
|
||||
content={contextInfo}
|
||||
contextName={getMainViewLabel(activeView)}
|
||||
onOpenChange={setInfoOpen}
|
||||
open={infoOpen}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import { Icon } from "../ui/Icon";
|
||||
|
||||
export interface AppSidebarProps {
|
||||
children: ReactNode;
|
||||
status: ReactNode;
|
||||
collapsed: boolean;
|
||||
responsiveRail?: boolean;
|
||||
onCollapsedChange: (collapsed: boolean) => void;
|
||||
}
|
||||
|
||||
export function AppSidebar({
|
||||
children,
|
||||
status,
|
||||
collapsed,
|
||||
responsiveRail = false,
|
||||
onCollapsedChange
|
||||
}: AppSidebarProps): ReactElement {
|
||||
return (
|
||||
<aside
|
||||
className={`md-shell-sidebar${collapsed ? " is-collapsed" : ""}${responsiveRail ? " is-responsive-rail" : ""}`}
|
||||
data-ui-region="sidebar"
|
||||
>
|
||||
{children ? <div className="md-shell-sidebar-scroll">{children}</div> : null}
|
||||
{status ? <div className="md-shell-sidebar-status" data-ui-region="sidebar-status">{status}</div> : null}
|
||||
<button
|
||||
aria-label={collapsed ? "Seitenleiste ausklappen" : "Seitenleiste einklappen"}
|
||||
className="md-shell-sidebar-toggle"
|
||||
onClick={() => onCollapsedChange(!collapsed)}
|
||||
title={collapsed ? "Seitenleiste ausklappen" : "Seitenleiste einklappen"}
|
||||
type="button"
|
||||
>
|
||||
<Icon name={collapsed ? "chevron-right" : "chevron-left"} size={14} />
|
||||
</button>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useEffect, useRef, type ReactElement } from "react";
|
||||
import { restoreFocus } from "../ui/focus";
|
||||
|
||||
export interface AvatarMenuAction {
|
||||
id: string;
|
||||
label: string;
|
||||
danger?: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export interface AvatarMenuProps {
|
||||
open: boolean;
|
||||
accountLabel: string;
|
||||
actions: AvatarMenuAction[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export type AvatarMenuKeyboardAction =
|
||||
| { type: "close" }
|
||||
| { type: "focus"; index: number };
|
||||
|
||||
export function getAvatarMenuKeyboardAction(
|
||||
key: string,
|
||||
currentIndex: number,
|
||||
itemCount: number
|
||||
): AvatarMenuKeyboardAction | null {
|
||||
if (key === "Escape") {
|
||||
return { type: "close" };
|
||||
}
|
||||
if (itemCount <= 0) {
|
||||
return null;
|
||||
}
|
||||
if (key === "Home") {
|
||||
return { type: "focus", index: 0 };
|
||||
}
|
||||
if (key === "End") {
|
||||
return { type: "focus", index: itemCount - 1 };
|
||||
}
|
||||
if (key === "ArrowDown") {
|
||||
return { type: "focus", index: (Math.max(currentIndex, -1) + 1) % itemCount };
|
||||
}
|
||||
if (key === "ArrowUp") {
|
||||
return { type: "focus", index: currentIndex <= 0 ? itemCount - 1 : currentIndex - 1 };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function AvatarMenu({ open, accountLabel, actions, onClose }: AvatarMenuProps): ReactElement | null {
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
|
||||
|
||||
const closeAndRestoreFocus = (): void => {
|
||||
const trigger = menuRef.current?.parentElement?.querySelector<HTMLButtonElement>('button[aria-haspopup="menu"]');
|
||||
onClose();
|
||||
restoreFocus(trigger ?? null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
const onPointerDown = (event: MouseEvent): void => {
|
||||
if (!menuRef.current?.parentElement?.contains(event.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", onPointerDown);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", onPointerDown);
|
||||
};
|
||||
}, [onClose, open]);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-label="Kontomenü"
|
||||
className="md-avatar-menu"
|
||||
onKeyDown={(event) => {
|
||||
const currentIndex = itemRefs.current.findIndex((item) => item === document.activeElement);
|
||||
const action = getAvatarMenuKeyboardAction(event.key, currentIndex, actions.length);
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (action.type === "close") {
|
||||
closeAndRestoreFocus();
|
||||
return;
|
||||
}
|
||||
itemRefs.current[action.index]?.focus();
|
||||
}}
|
||||
ref={menuRef}
|
||||
role="menu"
|
||||
>
|
||||
<div className="md-avatar-menu-account">{accountLabel}</div>
|
||||
{actions.map((action, index) => (
|
||||
<button
|
||||
autoFocus={index === 0}
|
||||
className={`md-avatar-menu-action${action.danger ? " is-danger" : ""}`}
|
||||
key={action.id}
|
||||
onClick={() => {
|
||||
action.onSelect();
|
||||
closeAndRestoreFocus();
|
||||
}}
|
||||
ref={(element) => {
|
||||
itemRefs.current[index] = element;
|
||||
}}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
||||
export interface OverlayHostProps {
|
||||
confirm?: ReactNode;
|
||||
onlineBackup?: ReactNode;
|
||||
diagnostics?: ReactNode;
|
||||
deleteConfirmation?: ReactNode;
|
||||
conflict?: ReactNode;
|
||||
accountCreate?: ReactNode;
|
||||
accountEdit?: ReactNode;
|
||||
keyStats?: ReactNode;
|
||||
linkPopup?: ReactNode;
|
||||
update?: ReactNode;
|
||||
toast?: ReactNode;
|
||||
accountContextMenu?: ReactNode;
|
||||
downloadContextMenu?: ReactNode;
|
||||
columnContextMenu?: ReactNode;
|
||||
historyContextMenu?: ReactNode;
|
||||
dropOverlay?: ReactNode;
|
||||
}
|
||||
|
||||
export function OverlayHost({
|
||||
confirm,
|
||||
onlineBackup,
|
||||
diagnostics,
|
||||
deleteConfirmation,
|
||||
conflict,
|
||||
accountCreate,
|
||||
accountEdit,
|
||||
keyStats,
|
||||
linkPopup,
|
||||
update,
|
||||
toast,
|
||||
accountContextMenu,
|
||||
downloadContextMenu,
|
||||
columnContextMenu,
|
||||
historyContextMenu,
|
||||
dropOverlay
|
||||
}: OverlayHostProps): ReactElement {
|
||||
return (
|
||||
<div className="md-overlay-host" id="md-overlay-host">
|
||||
{confirm}
|
||||
{onlineBackup}
|
||||
{diagnostics}
|
||||
{deleteConfirmation}
|
||||
{conflict}
|
||||
{accountCreate}
|
||||
{accountEdit}
|
||||
{keyStats}
|
||||
{linkPopup}
|
||||
{update}
|
||||
{toast}
|
||||
{accountContextMenu}
|
||||
{downloadContextMenu}
|
||||
{columnContextMenu}
|
||||
{historyContextMenu}
|
||||
{dropOverlay}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { useState, type ReactElement } from "react";
|
||||
import type { UpdateInstallProgress } from "../../shared/types";
|
||||
import { Dialog } from "../ui/Dialog";
|
||||
|
||||
export { getDialogFocusTarget as getUpdateDialogFocusTarget } from "../ui/Dialog";
|
||||
|
||||
export type UpdateExperienceState = "prompt" | UpdateInstallProgress["stage"];
|
||||
|
||||
export interface UpdateExperienceProgress {
|
||||
percent: number | null;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface UpdateExperienceProps {
|
||||
available: boolean;
|
||||
latestTag: string;
|
||||
currentVersion: string;
|
||||
releaseNotes: string;
|
||||
state: UpdateExperienceState;
|
||||
progress: UpdateExperienceProgress | number | null;
|
||||
open: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
onInstall: () => void;
|
||||
onLater: () => void;
|
||||
renderTrigger?: boolean;
|
||||
renderDialog?: boolean;
|
||||
}
|
||||
|
||||
const activeStates = new Set<UpdateExperienceState>([
|
||||
"starting",
|
||||
"downloading",
|
||||
"verifying",
|
||||
"launching"
|
||||
]);
|
||||
|
||||
export function UpdateExperience({
|
||||
available,
|
||||
latestTag,
|
||||
currentVersion,
|
||||
releaseNotes,
|
||||
state,
|
||||
progress,
|
||||
open,
|
||||
onOpen,
|
||||
onClose,
|
||||
onInstall,
|
||||
onLater,
|
||||
renderTrigger = true,
|
||||
renderDialog = true
|
||||
}: UpdateExperienceProps): ReactElement | null {
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
const progressInfo = typeof progress === "object" ? progress : null;
|
||||
const active = activeStates.has(state);
|
||||
const closable = !active;
|
||||
|
||||
if (!available && !open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{available && renderTrigger ? (
|
||||
<div
|
||||
className="md-update-anchor"
|
||||
onBlur={() => setTooltipOpen(false)}
|
||||
onFocus={() => setTooltipOpen(true)}
|
||||
onMouseEnter={() => setTooltipOpen(true)}
|
||||
onMouseLeave={() => setTooltipOpen(false)}
|
||||
>
|
||||
<button
|
||||
aria-describedby="md-update-tooltip"
|
||||
aria-label="Update verfügbar"
|
||||
className="md-update-trigger"
|
||||
onClick={onOpen}
|
||||
type="button"
|
||||
>
|
||||
Update verfügbar
|
||||
</button>
|
||||
<div
|
||||
aria-label="Update verfügbar"
|
||||
className={`md-update-tooltip${tooltipOpen ? " is-visible" : ""}`}
|
||||
id="md-update-tooltip"
|
||||
role="tooltip"
|
||||
>
|
||||
Eine neue Version ist bereit. Klicke hier, um sie zu installieren.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{renderDialog ? (
|
||||
<Dialog
|
||||
actions={state === "prompt" ? (
|
||||
<>
|
||||
<button className="btn" onClick={onLater} type="button">Später</button>
|
||||
<button className="btn primary" onClick={onInstall} type="button">Jetzt aktualisieren</button>
|
||||
</>
|
||||
) : null}
|
||||
actionsClassName="md-update-dialog-actions"
|
||||
backdropClassName="md-update-backdrop"
|
||||
bodyClassName="md-update-dialog-body"
|
||||
className={`md-update-dialog md-update-dialog-${state}`}
|
||||
closable={closable}
|
||||
headerClassName="md-update-dialog-header"
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
showCloseButton
|
||||
size="update"
|
||||
title="Update installieren"
|
||||
>
|
||||
{state === "prompt" ? (
|
||||
<>
|
||||
<p>{latestTag} ist verfügbar. Installierte Version: {currentVersion}.</p>
|
||||
{releaseNotes.trim() ? (
|
||||
<details className="md-update-release-notes">
|
||||
<summary>Changelog anzeigen</summary>
|
||||
<pre>{releaseNotes}</pre>
|
||||
</details>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="md-update-progress-text">{progressInfo?.text ?? "Update wird vorbereitet..."}</p>
|
||||
{state === "downloading" && progressInfo?.percent !== null && progressInfo?.percent !== undefined ? (
|
||||
<div
|
||||
aria-label="Update-Fortschritt"
|
||||
aria-valuemax={100}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={progressInfo.percent}
|
||||
className="md-update-progress-track"
|
||||
role="progressbar"
|
||||
>
|
||||
<div className="md-update-progress-fill" style={{ width: `${progressInfo.percent}%` }} />
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Dialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { IconName } from "../ui/Icon";
|
||||
|
||||
export type MainView = "downloads" | "collector" | "settings" | "history" | "statistics";
|
||||
export type ResponsiveShellMode = "full" | "compact" | "minimum";
|
||||
|
||||
export interface NavigationItem {
|
||||
id: MainView;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
const MAIN_NAVIGATION = [
|
||||
{ id: "downloads", label: "Downloads", icon: "download" },
|
||||
{ id: "collector", label: "Linksammler", icon: "collector" },
|
||||
{ id: "settings", label: "Einstellungen", icon: "settings" },
|
||||
{ id: "history", label: "Verlauf", icon: "history" },
|
||||
{ id: "statistics", label: "Statistiken", icon: "statistics" }
|
||||
] as const;
|
||||
|
||||
export function buildMainNavigation(active: MainView): NavigationItem[] {
|
||||
return MAIN_NAVIGATION.map((item) => ({ ...item, active: item.id === active }));
|
||||
}
|
||||
|
||||
export function getMainViewLabel(view: MainView): string {
|
||||
return MAIN_NAVIGATION.find((item) => item.id === view)?.label ?? view;
|
||||
}
|
||||
|
||||
export function getResponsiveShellMode(width: number): ResponsiveShellMode {
|
||||
if (width <= 1120) {
|
||||
return "minimum";
|
||||
}
|
||||
|
||||
if (width <= 1366) {
|
||||
return "compact";
|
||||
}
|
||||
|
||||
return "full";
|
||||
}
|
||||
@@ -0,0 +1,822 @@
|
||||
.md-runtime-root {
|
||||
--md-layer-avatar: 500;
|
||||
--md-layer-menu: 600;
|
||||
--md-layer-tooltip: 700;
|
||||
--md-layer-toast: 800;
|
||||
--md-layer-modal: 1000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-runtime-root.settings-active {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.md-runtime-root.settings-active input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
|
||||
.md-runtime-root.settings-active textarea,
|
||||
.md-runtime-root.settings-active [contenteditable]:not([contenteditable="false"]) {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.md-shell {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
grid-template-rows: 40px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
padding: 8px 16px 16px;
|
||||
overflow: hidden;
|
||||
background: var(--ui-canvas);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-shell-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 8px;
|
||||
background: var(--ui-surface);
|
||||
}
|
||||
|
||||
.md-shell-brand {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 16px;
|
||||
color: var(--ui-text);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.md-shell-navigation {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-shell-navigation-item {
|
||||
display: inline-flex;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--ui-text-secondary);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.md-shell-navigation-item:hover {
|
||||
background: var(--ui-hover);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-shell-navigation-item.is-active {
|
||||
background: var(--ui-active);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-shell-navigation-item:focus-visible,
|
||||
.md-shell-header-actions button:focus-visible,
|
||||
.md-shell-sidebar-toggle:focus-visible,
|
||||
.md-avatar-menu-action:focus-visible {
|
||||
outline: 2px solid var(--ui-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.md-shell-header-actions {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.md-update-anchor {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.md-update-trigger {
|
||||
display: inline-flex;
|
||||
height: 40px;
|
||||
min-width: 90px;
|
||||
padding: 0 14px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: var(--ui-primary);
|
||||
color: #0f0f0f;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.md-update-trigger:hover {
|
||||
background: var(--ui-primary-hover);
|
||||
}
|
||||
|
||||
.md-update-trigger:focus-visible,
|
||||
.md-update-dialog button:focus-visible,
|
||||
.md-update-release-notes summary:focus-visible {
|
||||
outline: 2px solid var(--ui-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.md-update-tooltip {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
z-index: var(--md-layer-tooltip);
|
||||
display: flex;
|
||||
width: 200px;
|
||||
min-height: 68px;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background: #4f4d4d;
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 35%);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.md-update-anchor:hover .md-update-tooltip,
|
||||
.md-update-anchor:focus-within .md-update-tooltip,
|
||||
.md-update-tooltip.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.md-update-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1300;
|
||||
display: grid;
|
||||
padding: 16px;
|
||||
place-items: center;
|
||||
background: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
.md-update-dialog {
|
||||
display: grid;
|
||||
width: min(548px, calc(100vw - 32px));
|
||||
min-height: 207px;
|
||||
max-height: calc(100vh - 32px);
|
||||
grid-template-rows: 56px minmax(92px, auto) 58px;
|
||||
overflow: auto;
|
||||
transform: translateY(-24px);
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 16px;
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 12px 40px rgb(0 0 0 / 45%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-update-dialog:not(.md-update-dialog-prompt) {
|
||||
grid-template-rows: 56px minmax(150px, auto);
|
||||
}
|
||||
|
||||
.md-update-dialog:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.md-update-dialog-header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
height: 56px;
|
||||
padding: 0 16px 0 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--ui-border);
|
||||
}
|
||||
|
||||
.md-update-dialog-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.md-update-dialog-close {
|
||||
display: grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--ui-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.md-update-dialog-close:hover {
|
||||
background: var(--ui-hover);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-update-dialog-body {
|
||||
min-width: 0;
|
||||
padding: 16px 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.md-update-dialog-body p {
|
||||
margin: 0;
|
||||
color: var(--ui-text-secondary);
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.md-update-release-notes {
|
||||
margin-top: 10px;
|
||||
color: var(--ui-text-secondary);
|
||||
}
|
||||
|
||||
.md-update-release-notes summary {
|
||||
width: max-content;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.md-update-release-notes pre {
|
||||
margin: 10px 0 0;
|
||||
color: var(--ui-text-secondary);
|
||||
font: inherit;
|
||||
line-height: 20px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.md-update-dialog-actions {
|
||||
display: flex;
|
||||
min-height: 58px;
|
||||
padding: 10px 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
border-top: 1px solid var(--ui-border);
|
||||
}
|
||||
|
||||
.md-update-progress-text {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.md-update-progress-track {
|
||||
height: 8px;
|
||||
margin-top: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: var(--ui-input);
|
||||
}
|
||||
|
||||
.md-update-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--ui-accent);
|
||||
}
|
||||
|
||||
.md-application-menu-tree {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.md-application-menu-tree .menu-bar-trigger {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.md-download-sidebar-content {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.md-download-sidebar-search {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: var(--ui-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.md-download-sidebar-search input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.md-download-sidebar-actions {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.md-download-sidebar-actions .btn {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.md-runtime-view-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-runtime-view-content > * {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.md-shell-workspace {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.md-shell.has-sidebar .md-shell-workspace {
|
||||
grid-template-columns: 270px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.md-shell.has-collapsed-sidebar .md-shell-workspace {
|
||||
grid-template-columns: 56px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.md-shell-sidebar,
|
||||
.md-shell-main {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 8px;
|
||||
background: var(--ui-surface);
|
||||
}
|
||||
|
||||
.md-shell-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-scroll {
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-status {
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border-top: 1px solid var(--ui-border);
|
||||
color: var(--ui-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.md-shell-sidebar.is-collapsed :where(.md-shell-sidebar-scroll, .md-shell-sidebar-status) {
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-toggle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -8px;
|
||||
z-index: 5;
|
||||
display: grid;
|
||||
width: 14px;
|
||||
height: 42px;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
transform: translateY(-50%);
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 4px;
|
||||
background: var(--ui-input);
|
||||
color: var(--ui-text-muted);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.md-shell-sidebar.is-responsive-rail .md-shell-sidebar-toggle {
|
||||
top: 12px;
|
||||
right: auto;
|
||||
left: 11px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.md-shell-sidebar:hover .md-shell-sidebar-toggle,
|
||||
.md-shell-sidebar-toggle:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.md-shell-main {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.md-shell-toolbar {
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.md-shell-content {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-shell-footer {
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid var(--ui-border);
|
||||
}
|
||||
|
||||
.md-avatar-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.md-avatar-trigger {
|
||||
display: grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--ui-active);
|
||||
color: var(--ui-text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.md-avatar-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
right: 0;
|
||||
z-index: var(--md-layer-avatar);
|
||||
display: flex;
|
||||
width: 174px;
|
||||
min-height: 106px;
|
||||
padding: 8px;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 8px;
|
||||
background: var(--ui-surface);
|
||||
}
|
||||
|
||||
.md-avatar-menu-account {
|
||||
overflow: hidden;
|
||||
padding: 4px 8px 8px;
|
||||
color: var(--ui-text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.md-avatar-menu-action {
|
||||
min-height: 30px;
|
||||
padding: 5px 8px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--ui-text-secondary);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.md-avatar-menu-action:hover {
|
||||
background: var(--ui-hover);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-avatar-menu-action.is-danger {
|
||||
color: var(--ui-danger);
|
||||
}
|
||||
|
||||
.md-application-menu-tree :where(.menu-dropdown, .menu-submenu-dropdown) {
|
||||
z-index: var(--md-layer-menu);
|
||||
}
|
||||
|
||||
.md-overlay-host {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.md-dialog-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--md-layer-modal);
|
||||
display: grid;
|
||||
padding: 20px;
|
||||
place-items: center;
|
||||
background: var(--ui-overlay);
|
||||
}
|
||||
|
||||
.md-dialog {
|
||||
display: grid;
|
||||
width: min(560px, calc(100vw - 40px));
|
||||
max-height: calc(100vh - 40px);
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 12px 40px rgb(0 0 0 / 45%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-dialog.md-dialog-size-account {
|
||||
width: min(660px, calc(100vw - 40px));
|
||||
}
|
||||
|
||||
.md-dialog.md-dialog-size-wide {
|
||||
width: min(760px, calc(100vw - 40px));
|
||||
}
|
||||
|
||||
.md-dialog.md-dialog-size-update {
|
||||
width: min(548px, calc(100vw - 32px));
|
||||
}
|
||||
|
||||
.md-dialog-header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 56px;
|
||||
padding: 12px 16px 12px 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--ui-border);
|
||||
}
|
||||
|
||||
.md-dialog-header h2 {
|
||||
margin: 0;
|
||||
color: var(--ui-text);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.md-dialog-close {
|
||||
display: grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--ui-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.md-dialog-close:hover {
|
||||
background: var(--ui-hover);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-dialog-body {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.md-dialog-body > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.md-dialog-body > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.md-dialog-description,
|
||||
.md-dialog-body p {
|
||||
color: var(--ui-text-secondary);
|
||||
}
|
||||
|
||||
.md-dialog-actions {
|
||||
min-height: 58px;
|
||||
padding: 10px 16px;
|
||||
border-top: 1px solid var(--ui-border);
|
||||
background: var(--ui-surface);
|
||||
}
|
||||
|
||||
.md-dialog :where(button, summary, input, select, textarea):focus-visible,
|
||||
.md-context-menu [role="menuitem"]:focus-visible {
|
||||
outline: 2px solid var(--ui-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.md-context-menu {
|
||||
z-index: var(--md-layer-menu);
|
||||
max-width: calc(100vw - 8px);
|
||||
max-height: calc(100vh - 8px);
|
||||
overflow: auto;
|
||||
border-color: var(--ui-border);
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 35%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-context-menu .ctx-menu-item {
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-context-menu .ctx-menu-item:hover,
|
||||
.md-context-menu .ctx-menu-item:focus-visible {
|
||||
background: var(--ui-hover);
|
||||
}
|
||||
|
||||
.md-context-menu .ctx-menu-item.ctx-danger {
|
||||
color: var(--ui-danger);
|
||||
}
|
||||
|
||||
.md-context-menu .ctx-menu-sep {
|
||||
background: var(--ui-border);
|
||||
}
|
||||
|
||||
.md-context-menu .ctx-menu-sub.is-keyboard-open > .ctx-menu-sub-items {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.md-toast {
|
||||
right: 20px;
|
||||
bottom: 84px;
|
||||
z-index: var(--md-layer-toast);
|
||||
max-width: min(420px, calc(100vw - 40px));
|
||||
border-color: var(--ui-border);
|
||||
background: var(--ui-surface);
|
||||
color: var(--ui-text);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 35%);
|
||||
}
|
||||
|
||||
.md-drop-overlay {
|
||||
z-index: var(--md-layer-modal);
|
||||
pointer-events: none;
|
||||
background: var(--ui-overlay);
|
||||
border-color: color-mix(in srgb, var(--ui-accent) 72%, transparent);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-overlay-host .md-dialog-backdrop {
|
||||
z-index: var(--md-layer-modal);
|
||||
padding: 20px;
|
||||
background: var(--ui-overlay);
|
||||
}
|
||||
|
||||
.md-overlay-host .md-dialog {
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border-color: var(--ui-border);
|
||||
border-radius: 16px;
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 12px 40px rgb(0 0 0 / 45%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-overlay-host .md-context-menu {
|
||||
z-index: var(--md-layer-menu);
|
||||
border-color: var(--ui-border);
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 35%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-overlay-host .md-toast {
|
||||
bottom: 84px;
|
||||
z-index: var(--md-layer-toast);
|
||||
border-color: var(--ui-border);
|
||||
background: var(--ui-surface);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 35%);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
.md-overlay-host .md-drop-overlay {
|
||||
z-index: var(--md-layer-modal);
|
||||
pointer-events: none;
|
||||
background: var(--ui-overlay);
|
||||
color: var(--ui-text);
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.md-shell.is-compact .md-shell-navigation-item span,
|
||||
.md-shell.is-compact .md-shell-brand,
|
||||
.md-shell.is-minimum .md-shell-navigation-item span,
|
||||
.md-shell.is-minimum .md-shell-brand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md-shell.is-compact .md-shell-header,
|
||||
.md-shell.is-minimum .md-shell-header {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.md-shell.is-compact .md-shell-navigation,
|
||||
.md-shell.is-minimum .md-shell-navigation {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-shell.is-compact .md-shell-navigation-item,
|
||||
.md-shell.is-minimum .md-shell-navigation-item {
|
||||
width: 36px;
|
||||
flex: 0 0 36px;
|
||||
padding-inline: 9px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.md-shell.is-compact .md-shell-header-actions,
|
||||
.md-shell.is-minimum .md-shell-header-actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.md-shell.is-minimum {
|
||||
gap: 6px;
|
||||
padding: 6px 8px 8px;
|
||||
}
|
||||
|
||||
.md-shell.is-minimum .md-shell-workspace {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.md-dialog-backdrop,
|
||||
.md-overlay-host .md-dialog-backdrop,
|
||||
.md-update-backdrop {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.md-dialog,
|
||||
.md-dialog.md-dialog-size-account,
|
||||
.md-dialog.md-dialog-size-wide,
|
||||
.md-dialog.md-dialog-size-update,
|
||||
.md-update-dialog {
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 24px);
|
||||
}
|
||||
|
||||
.md-dialog-actions,
|
||||
.md-update-dialog-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.md-shell *,
|
||||
.md-shell *::before,
|
||||
.md-shell *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user