Compare commits

...
Author SHA1 Message Date
EthanHealy01 600add1474 run frontend fix 2026-05-27 12:42:22 +01:00
EthanHealy01 8451b06d53 chore: set aiEngine env vars automatically in dev:all
When AIENGINE_URL is provided (i.e. task dev:all), also set
AIENGINE_ENABLED=true and AIENGINE_TIMEOUTSECONDS=120 so the backend
picks up the full AI engine config without needing it in settings.yml.
2026-05-27 12:12:42 +01:00
EthanHealy01 7bb3024dc9 feat: add midnight theme and fix theme locations
Adds midnight dark theme, fixes broken theme variable locations, adds
clean-unused-theme-vars script, and updates RainbowThemeProvider and
related theme constants.
2026-05-27 11:37:49 +01:00
EthanHealy01 18cd2baec0 feat: add Agents UI to proprietary right sidebar
Adds agents panel to the right sidebar, gated behind aiEngineEnabled.
Includes RightSidebar refactor, FullscreenToolPanel, core stubs, and
proprietary AgentsPanel with Stirling hero CTA and coming-soon agents.
2026-05-27 11:14:19 +01:00
47 changed files with 4900 additions and 705 deletions
+3 -2
View File
@@ -157,7 +157,9 @@ app/proprietary/build
common/build
proprietary/build
stirling-pdf/build
frontend/editor/src-tauri/provisioner/target
frontend/src-tauri/provisioner/target
frontend/src-tauri/target
frontend/src-tauri/runtime/
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -174,7 +176,6 @@ venv.bak/
# Env files (secrets / local overrides). Subproject .gitignore files whitelist any committed defaults.
.env*
!.env.saas.example
# VS Code
/.vscode/**/*
+2 -2
View File
@@ -25,9 +25,9 @@ tasks:
env:
SERVER_PORT: '{{.PORT}}'
cmds:
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true AIENGINE_TIMEOUTSECONDS=120 {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
platforms: [windows]
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true AIENGINE_TIMEOUTSECONDS=120 {{end}}./gradlew :stirling-pdf:bootRun'
platforms: [linux, darwin]
dev:bundled:
+15
View File
@@ -257,6 +257,7 @@ tasks:
- task: lint
- task: format:check
- task: test
- task: theme:check-unused-vars
check:all:
desc: "Full CI quality gate"
@@ -298,3 +299,17 @@ tasks:
deps: [install]
cmds:
- node editor/scripts/generate-licenses.js
theme:clean-unused-vars:
desc: "Remove unused CSS variables from theme.css"
deps: [install]
vars:
REMOVE: '{{.REMOVE | default "false"}}'
cmds:
- node editor/scripts/clean-unused-theme-vars.js{{if eq .REMOVE "true"}} --remove{{end}} {{.CLI_ARGS}}
theme:check-unused-vars:
desc: "Fail if theme.css contains unused CSS variables"
deps: [install]
cmds:
- node editor/scripts/clean-unused-theme-vars.js --check
+1 -1
View File
@@ -74,7 +74,7 @@ tasks:
vars:
PORT: '{{.BACKEND_PORT}}'
AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}'
- task: frontend:dev:prototypes
- task: frontend:dev
vars:
PORT: '{{.FRONTEND_PORT}}'
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
@@ -277,6 +277,9 @@ public class ConfigController {
// Premium/Enterprise settings
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
// AI Engine settings
configData.put("aiEngineEnabled", applicationProperties.getAiEngine().isEnabled());
// Timestamp TSA settings — single source of truth for presets + admin URLs
ApplicationProperties.Security.Timestamp tsConfig =
applicationProperties.getSecurity().getTimestamp();
@@ -2672,6 +2672,47 @@ title = "Change Permissions"
[changePermissions.tooltip.warning]
text = "To make these permissions unchangeable, use the Add Password tool to set an owner password."
[agents]
section_title = "Agents"
fullscreen_title = "Stirling Agents"
stirling_name = "Stirling"
stirling_full_name = "Stirling General Agent"
stirling_tooltip = "Stirling agent"
stirling_description = "Your general-purpose PDF assistant"
stirling_long_description = "General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents."
back_to_tools = "Back to tools"
coming_soon = "Coming soon"
view_all = "View all agents"
show_less = "Show less"
start_chat = "Start chatting"
[chat.header]
settings = "Agent settings"
agentMenu = "Stirling agent options"
clearChat = "Clear chat"
[chat.input]
placeholder = "What do you want to do?"
send = "Send message"
attach = "Attach files"
[chat.quickActions]
heading = "Get started"
openFromComputer = "Open from computer"
browseYourFiles = "Browse your files"
rotateOne = "Rotate this document"
rotateMany = "Rotate these documents"
compressOne = "Compress this document"
compressMany = "Compress these documents"
mergeMany = "Merge these {{count}} documents into 1"
splitOne = "Split this document"
convertOne = "Convert this document to PDF"
convertMany = "Convert these documents to PDF"
fileSummary_one = "1 file in workbench ({{types}})"
fileSummary_other = "{{count}} files in workbench ({{types}})"
moreFiles = "+{{count}} more"
removeFile = "Remove {{name}}"
[chat.progress]
analyzing = "Analysing your request..."
calling_engine = "AI is thinking..."
@@ -6864,6 +6905,7 @@ theme = "Theme"
themeDark = "Dark"
themeDescription = "Switch between light and dark mode"
themeLight = "Light"
themeMidnight = "Midnight"
title = "General"
user = "User"
@@ -7910,6 +7952,11 @@ expand = "Expand panel"
placeholder = "Choose a tool to get started"
premiumFeature = "Premium feature:"
search = "Search tools"
toolsHeader = "Tools"
viewAllTools = "View all tools"
backToDefault = "Back"
backToAllTools = "Back to all tools"
goBack = "Go back"
[toolPanel.fullscreen]
comingSoon = "Coming soon:"
@@ -0,0 +1,217 @@
#!/usr/bin/env node
/**
* Finds and optionally removes unused CSS custom properties from theme.css.
*
* Usage:
* node scripts/clean-unused-theme-vars.js # dry run: report only
* node scripts/clean-unused-theme-vars.js --check # CI mode: exit 1 if any unused
* node scripts/clean-unused-theme-vars.js --remove # remove unused declarations
* node scripts/clean-unused-theme-vars.js --verbose # show all usage counts
*/
const fs = require("fs");
const path = require("path");
const REMOVE = process.argv.includes("--remove");
const CHECK = process.argv.includes("--check");
const VERBOSE =
process.argv.includes("--verbose") || process.argv.includes("-v");
const EDITOR_ROOT = path.join(__dirname, "..");
const THEME_CSS_PATH = path.join(EDITOR_ROOT, "src/core/styles/theme.css");
const SCAN_EXTENSIONS = new Set([
".ts",
".tsx",
".css",
".js",
".jsx",
".mts",
".mjs",
]);
const SKIP_DIRS = new Set([
"node_modules",
"dist",
".git",
"target",
"coverage",
".turbo",
]);
// ─── Parse declarations ───────────────────────────────────────────────────────
function parseDeclarations(content) {
const lines = content.split("\n");
// Map from varName -> array of 0-based line indices where it is declared
const declarations = new Map();
for (let i = 0; i < lines.length; i++) {
const match = lines[i].match(/^\s*(--[\w-]+)\s*:/);
if (match) {
const name = match[1];
if (!declarations.has(name)) declarations.set(name, []);
declarations.get(name).push(i);
}
}
return declarations;
}
// ─── Scan for usages ─────────────────────────────────────────────────────────
const VAR_USAGE_RE = /var\((--[\w-]+)/g;
function collectUsagesInFile(filePath, used) {
let content;
try {
content = fs.readFileSync(filePath, "utf8");
} catch {
return;
}
VAR_USAGE_RE.lastIndex = 0;
let match;
while ((match = VAR_USAGE_RE.exec(content)) !== null) {
used.add(match[1]);
}
}
function scanDir(dir, used) {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
scanDir(fullPath, used);
} else if (SCAN_EXTENSIONS.has(path.extname(entry.name))) {
collectUsagesInFile(fullPath, used);
}
}
}
// ─── Remove unused declarations ───────────────────────────────────────────────
function removeDeclarations(content, unusedNames) {
const lines = content.split("\n");
const filtered = [];
let skipping = false;
for (const line of lines) {
if (skipping) {
// Still consuming a multi-line value — stop when we hit the terminating semicolon
if (line.includes(";")) skipping = false;
continue;
}
const match = line.match(/^\s*(--[\w-]+)\s*:/);
if (match && unusedNames.has(match[1])) {
// If the value doesn't end on this line, skip subsequent lines too
if (!line.includes(";")) skipping = true;
continue;
}
filtered.push(line);
}
// Collapse runs of 3+ blank lines down to 2 (avoids gaps from removed blocks)
const collapsed = [];
let blankRun = 0;
for (const line of filtered) {
if (line.trim() === "") {
blankRun++;
if (blankRun <= 2) collapsed.push(line);
} else {
blankRun = 0;
collapsed.push(line);
}
}
return collapsed.join("\n");
}
// ─── Main ─────────────────────────────────────────────────────────────────────
function main() {
if (!fs.existsSync(THEME_CSS_PATH)) {
console.error("theme.css not found at:", THEME_CSS_PATH);
process.exit(1);
}
const themeCss = fs.readFileSync(THEME_CSS_PATH, "utf8");
const declarations = parseDeclarations(themeCss);
console.log(`Found ${declarations.size} declared CSS variables in theme.css`);
console.log("Scanning source files for usages...");
const used = new Set();
scanDir(EDITOR_ROOT, used);
const unused = new Set();
for (const name of declarations.keys()) {
if (!used.has(name)) unused.add(name);
}
if (VERBOSE) {
const allNames = [...declarations.keys()].sort();
for (const name of allNames) {
const status = used.has(name) ? "used " : "UNUSED";
console.log(` ${status} ${name}`);
}
console.log();
}
if (unused.size === 0) {
console.log("No unused CSS variables found.");
return;
}
const sortedUnused = [...unused].sort();
console.log(`\nUnused variables (${unused.size}):`);
for (const name of sortedUnused) {
const lineNums = declarations
.get(name)
.map((i) => i + 1)
.join(", ");
console.log(
` ${name} (line${declarations.get(name).length > 1 ? "s" : ""} ${lineNums})`,
);
}
if (CHECK) {
console.error(
"\ntheme.css has unused CSS variables. Run the following to fix:",
);
console.error("\n task frontend:theme:clean-unused-vars REMOVE=true\n");
process.exit(1);
}
if (!REMOVE) {
console.log(
"\nDry run — no changes written. Re-run with --remove to delete unused declarations.",
);
return;
}
const updated = removeDeclarations(themeCss, unused);
fs.writeFileSync(THEME_CSS_PATH, updated, "utf8");
// Count declarations removed
const declsRemoved = [...unused].reduce(
(sum, name) => sum + declarations.get(name).length,
0,
);
console.log(
`\nRemoved ${declsRemoved} declaration${declsRemoved !== 1 ? "s" : ""} (${unused.size} unique variable${unused.size !== 1 ? "s" : ""}) from theme.css`,
);
}
main();
@@ -0,0 +1,53 @@
/**
* Core stubs for the right-rail Agents UI.
*
* The real implementations live in {@code proprietary/components/agents/AgentsPanel.tsx}
* and shadow these stubs via the {@code @app/*} alias cascade when the proprietary
* build is active. Core builds render nothing, so the right rail collapses to the
* tool list unchanged.
*/
/** Whether the right rail should reserve space for agents UI. False in core. */
export function useAgentsEnabled(): boolean {
return false;
}
/**
* Whether the agent chat overlay is currently open. Core builds have no chat,
* so this always returns false. Proprietary builds bridge to the ChatContext.
* Used by {@code RightSidebar} so the fullscreen tool picker can yield to the
* chat overlay just like it yields to a selected tool.
*/
export function useAgentChatOpen(): boolean {
return false;
}
/** Inline "Agents" section rendered above the tool list in {@code ToolPicker}. */
export function AgentsSection() {
return null;
}
/**
* Icon-only agent button rendered in the collapsed (minimised) right rail.
* Returns null in core; proprietary renders the Stirling agent shortcut.
*/
export function AgentsCollapsedButton(_props: { onExpand: () => void }) {
return null;
}
/**
* Full-rail chat overlay rendered inside {@code ToolPanel}. Covers the panel
* (including the search bar) when an agent conversation is active.
*/
export function AgentsChatOverlay() {
return null;
}
/**
* Agents card rendered inside the fullscreen tool picker. Matches the visual
* language of the fullscreen category cards (gradient border, title, items).
* Returns null in core; proprietary renders the Stirling agent.
*/
export function AgentsFullscreenSection() {
return null;
}
@@ -75,7 +75,7 @@
box-shadow: var(--shadow-md);
overflow: hidden;
position: relative;
background: var(--bg-surface);
background: white;
transition:
box-shadow 0.15s ease,
transform 0.2s ease;
@@ -1,4 +1,4 @@
import { createContext, useContext, ReactNode } from "react";
import { createContext, useContext, ReactNode, useEffect } from "react";
import { MantineProvider } from "@mantine/core";
import { useRainbowTheme } from "@app/hooks/useRainbowTheme";
import { mantineTheme } from "@app/theme/mantineTheme";
@@ -36,9 +36,20 @@ interface RainbowThemeProviderProps {
export function RainbowThemeProvider({ children }: RainbowThemeProviderProps) {
const rainbowTheme = useRainbowTheme();
// Determine the Mantine color scheme
// Determine the Mantine color scheme (midnight and rainbow both use Mantine dark)
const mantineColorScheme =
rainbowTheme.themeMode === "rainbow" ? "dark" : rainbowTheme.themeMode;
rainbowTheme.themeMode === "rainbow" ||
rainbowTheme.themeMode === "midnight"
? "dark"
: rainbowTheme.themeMode;
// Apply data-color-scheme attribute to html element for CSS midnight overrides
useEffect(() => {
document.documentElement.setAttribute(
"data-color-scheme",
rainbowTheme.themeMode,
);
}, [rainbowTheme.themeMode]);
return (
<RainbowThemeContext.Provider value={rainbowTheme}>
@@ -49,7 +49,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
const { t } = useTranslation();
const { preferences, updatePreference } = usePreferences();
const { config } = useAppConfig();
const { toggleTheme, themeMode } = useRainbowThemeContext();
const { themeMode } = useRainbowThemeContext();
const [fileLimitInput, setFileLimitInput] = useState<number | string>(
preferences.autoUnzipFileLimit,
);
@@ -380,10 +380,10 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
</Text>
</div>
<SegmentedControl
value={themeMode === "rainbow" ? "dark" : themeMode}
onChange={(val) => {
if ((themeMode === "dark") !== (val === "dark")) toggleTheme();
}}
value={themeMode === "rainbow" ? "dark" : (themeMode as string)}
onChange={(val) =>
updatePreference("theme", val as "light" | "dark" | "midnight")
}
data={[
{
label: t("settings.general.themeLight", "Light"),
@@ -393,6 +393,10 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
label: t("settings.general.themeDark", "Dark"),
value: "dark",
},
{
label: t("settings.general.themeMidnight", "Midnight"),
value: "midnight",
},
]}
/>
</div>
@@ -0,0 +1,102 @@
import { useEffect, useMemo } from "react";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import { useIsMobile } from "@app/hooks/useIsMobile";
import { usePreferences } from "@app/contexts/PreferencesContext";
import { useWorkbenchBar } from "@app/contexts/WorkbenchBarContext";
import {
AgentsFullscreenSection,
useAgentChatOpen,
useAgentsEnabled,
} from "@app/components/agents/AgentsPanel";
import FullscreenToolSurface from "@app/components/tools/FullscreenToolSurface";
import { ToolId } from "@app/types/toolId";
import type { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
/** Derives whether the fullscreen tool picker is currently expanded. */
export function useIsFullscreenExpanded(): boolean {
const { toolPanelMode, leftPanelView, readerMode } = useToolWorkflow();
const isMobile = useIsMobile();
const agentChatOpen = useAgentChatOpen();
return (
toolPanelMode === "fullscreen" &&
leftPanelView === "toolPicker" &&
!isMobile &&
!readerMode &&
!agentChatOpen
);
}
interface FullscreenToolPanelProps {
geometry: ToolPanelGeometry | null;
}
/**
* Self-contained fullscreen tool picker. Renders null when inactive, and takes
* over the right rail (via FullscreenToolSurface) when fullscreen mode is on.
* Geometry is computed by the parent (RightSidebar) so its useLayoutEffect runs
* after the ref div is committed, ensuring toolPanelRef.current is always set.
*/
export function FullscreenToolPanel({ geometry }: FullscreenToolPanelProps) {
const {
toolPanelMode,
setToolPanelMode,
leftPanelView,
readerMode,
searchQuery,
setSearchQuery,
filteredTools,
toolRegistry,
selectedToolKey,
handleToolSelect,
} = useToolWorkflow();
const isMobile = useIsMobile();
const agentsEnabled = useAgentsEnabled();
const agentChatOpen = useAgentChatOpen();
const { setAllButtonsDisabled } = useWorkbenchBar();
const { preferences, updatePreference } = usePreferences();
const fullscreenExpanded =
toolPanelMode === "fullscreen" &&
leftPanelView === "toolPicker" &&
!isMobile &&
!readerMode &&
!agentChatOpen;
useEffect(() => {
setAllButtonsDisabled(fullscreenExpanded);
}, [fullscreenExpanded, setAllButtonsDisabled]);
const matchedTextMap = useMemo(() => {
const map = new Map<string, string>();
filteredTools.forEach(({ item: [id], matchedText }) => {
if (matchedText) map.set(id, matchedText);
});
return map;
}, [filteredTools]);
if (!fullscreenExpanded) return null;
return (
<FullscreenToolSurface
searchQuery={searchQuery}
toolRegistry={toolRegistry}
filteredTools={filteredTools}
selectedToolKey={selectedToolKey}
showDescriptions={preferences.showLegacyToolDescriptions}
matchedTextMap={matchedTextMap}
onSearchChange={setSearchQuery}
onSelect={(id: ToolId) => handleToolSelect(id)}
onToggleDescriptions={() =>
updatePreference(
"showLegacyToolDescriptions",
!preferences.showLegacyToolDescriptions,
)
}
onExitFullscreenMode={() => setToolPanelMode("sidebar")}
geometry={geometry}
agentsSlot={
agentsEnabled && !searchQuery ? <AgentsFullscreenSection /> : null
}
/>
);
}
@@ -1,4 +1,5 @@
import { useRef } from "react";
import { createPortal } from "react-dom";
import { ScrollArea, Switch } from "@mantine/core";
import { useTranslation } from "react-i18next";
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
@@ -6,8 +7,6 @@ import FullscreenToolList from "@app/components/tools/FullscreenToolList";
import { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
import { ToolId } from "@app/types/toolId";
import { useFocusTrap } from "@app/hooks/useFocusTrap";
import { LogoIcon } from "@app/components/shared/LogoIcon";
import { Wordmark } from "@app/components/shared/Wordmark";
import "@app/components/tools/ToolPanel.css";
import { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
@@ -26,6 +25,8 @@ interface FullscreenToolSurfaceProps {
onToggleDescriptions: () => void;
onExitFullscreenMode: () => void;
geometry: ToolPanelGeometry | null;
/** Optional agents block rendered above the tool list. */
agentsSlot?: React.ReactNode;
}
const FullscreenToolSurface = ({
@@ -40,6 +41,7 @@ const FullscreenToolSurface = ({
onToggleDescriptions,
onExitFullscreenMode: _onExitFullscreenMode,
geometry,
agentsSlot,
}: FullscreenToolSurfaceProps) => {
const { t } = useTranslation();
const surfaceRef = useRef<HTMLDivElement>(null);
@@ -47,18 +49,16 @@ const FullscreenToolSurface = ({
// Enable focus trap when surface is active
useFocusTrap(surfaceRef, true);
const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo");
if (!geometry) return null;
const style = geometry
? {
left: `${geometry.left}px`,
top: `${geometry.top}px`,
width: `${geometry.width}px`,
height: `${geometry.height}px`,
}
: undefined;
const style = {
left: `${geometry.left}px`,
top: `${geometry.top}px`,
width: `${geometry.width}px`,
height: `${geometry.height}px`,
};
return (
const surface = (
<div
className="tool-panel__fullscreen-surface"
style={style}
@@ -70,16 +70,6 @@ const FullscreenToolSurface = ({
data-tour="tool-panel"
>
<div ref={surfaceRef} className="tool-panel__fullscreen-surface-inner">
<header className="tool-panel__fullscreen-header">
<div className="tool-panel__fullscreen-brand">
<LogoIcon className="tool-panel__fullscreen-brand-icon" />
<Wordmark
alt={brandAltText}
className="tool-panel__fullscreen-brand-text"
/>
</div>
</header>
<div className="tool-panel__fullscreen-controls">
<ToolSearch
value={searchQuery}
@@ -102,6 +92,9 @@ const FullscreenToolSurface = ({
className="tool-panel__fullscreen-scroll"
offsetScrollbars
>
{agentsSlot && (
<div className="tool-panel__fullscreen-agents">{agentsSlot}</div>
)}
<FullscreenToolList
filteredTools={filteredTools}
searchQuery={searchQuery}
@@ -115,6 +108,9 @@ const FullscreenToolSurface = ({
</div>
</div>
);
if (typeof document === "undefined") return surface;
return createPortal(surface, document.body);
};
export default FullscreenToolSurface;
@@ -0,0 +1,333 @@
import { useMemo, useState } from "react";
import { ActionIcon } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import { useSidebarContext } from "@app/contexts/SidebarContext";
import rainbowStyles from "@app/styles/rainbow.module.css";
import { useIsMobile } from "@app/hooks/useIsMobile";
import ToolPanel from "@app/components/tools/ToolPanel";
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
import {
AgentsChatOverlay,
AgentsCollapsedButton,
AgentsSection,
useAgentsEnabled,
} from "@app/components/agents/AgentsPanel";
import { useFavoriteToolItems } from "@app/hooks/tools/useFavoriteToolItems";
import { useToolSections } from "@app/hooks/useToolSections";
import type { SubcategoryGroup } from "@app/hooks/useToolSections";
import { ToolIcon } from "@app/components/shared/ToolIcon";
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
import { withViewTransition } from "@app/utils/viewTransition";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import CloseIcon from "@mui/icons-material/Close";
import { ToolId } from "@app/types/toolId";
import type { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
import {
FullscreenToolPanel,
useIsFullscreenExpanded,
} from "@app/components/tools/FullscreenToolPanel";
import { useToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
import "@app/components/tools/ToolPanel.css";
/**
* Right-side rail wrapping the tool panel.
*
* Owns the rail-level concerns: collapse/expand chrome, the AGENTS top label,
* the collapsed strip (agent button + favourite/recommended icon shortcuts),
* and the agents chat overlay. Fullscreen takeover lives in FullscreenToolPanel.
*/
export default function RightSidebar() {
const { t } = useTranslation();
const { isRainbowMode } = useRainbowThemeContext();
const { sidebarRefs } = useSidebarContext();
const { toolPanelRef, quickAccessRef } = sidebarRefs;
const isMobile = useIsMobile();
const {
leftPanelView,
isPanelVisible,
searchQuery,
filteredTools,
toolRegistry,
setSearchQuery,
selectedToolKey,
handleToolSelect,
handleBackToTools,
setLeftPanelView,
setReaderMode,
setSidebarsVisible,
sidebarsVisible,
readerMode,
favoriteTools,
} = useToolWorkflow();
const agentsEnabled = useAgentsEnabled();
const fullscreenExpanded = useIsFullscreenExpanded();
const fullscreenGeometry = useToolPanelGeometry({
enabled: fullscreenExpanded,
toolPanelRef,
quickAccessRef,
});
const handleExpand = () => {
withViewTransition(() => {
if (readerMode) setReaderMode(false);
if (leftPanelView === "hidden") setLeftPanelView("toolPicker");
if (!sidebarsVisible) setSidebarsVisible(true);
});
};
const handleCollapse = () => {
withViewTransition(() => setLeftPanelView("hidden"));
};
const [allToolsView, setAllToolsView] = useState(false);
const handleShowAllTools = () => {
withViewTransition(() => setAllToolsView(true));
};
const handleBackToDefault = () => {
withViewTransition(() => {
setAllToolsView(false);
setSearchQuery("");
});
};
// The header shows [back] [search] when we have somewhere to go back to —
// i.e. the user is in a specific tool, or already in the all-tools/search view.
const inToolView = leftPanelView !== "toolPicker";
// Show X (close) button only when there's somewhere to go back to.
const showCloseButton = inToolView || allToolsView;
// Show search input whenever there's a close button, or when agents are off and
// we're in the default tool-picker view (search filters the full list inline).
const showHeaderSearch =
showCloseButton || (!agentsEnabled && leftPanelView === "toolPicker");
const handleHeaderBack = () => {
if (inToolView) {
withViewTransition(() => handleBackToTools());
} else {
handleBackToDefault();
}
};
const handleToolSelectWithTransition = (id: ToolId) => {
withViewTransition(() => handleToolSelect(id));
};
// Typing in the header search while inside a tool exits the tool and lifts the
// panel into the all-tools view so the user immediately sees search results.
const handleHeaderSearchChange = (value: string) => {
if (inToolView) {
withViewTransition(() => {
handleBackToTools();
setAllToolsView(true);
setSearchQuery(value);
});
return;
}
setSearchQuery(value);
};
const activeTool: ToolRegistryEntry | null =
inToolView && selectedToolKey
? (toolRegistry[selectedToolKey as ToolId] ?? null)
: null;
// Agents header + section is hidden when:
// - the AI engine is off, or
// - the user is in the all-tools view, or
// - a specific tool is being rendered (leftPanelView ≠ "toolPicker").
const showAgents =
agentsEnabled && !allToolsView && leftPanelView === "toolPicker";
const computedWidth = () => {
if (isMobile) return "100%";
if (!isPanelVisible) return "3.5rem";
return "18.5rem";
};
// Collapsed rail: show favourites + recommended tools as icons.
const favoriteToolItems = useFavoriteToolItems(favoriteTools, toolRegistry);
const { sections: collapsedSections } = useToolSections(filteredTools);
const collapsedQuickSection = useMemo(
() => collapsedSections.find((s) => s.key === "quick"),
[collapsedSections],
);
const collapsedRecommendedItems = useMemo(() => {
if (!collapsedQuickSection) return [];
const items: Array<{ id: ToolId; tool: ToolRegistryEntry }> = [];
collapsedQuickSection.subcategories.forEach((sc: SubcategoryGroup) =>
sc.tools.forEach((entry) =>
items.push({ id: entry.id as ToolId, tool: entry.tool }),
),
);
return items;
}, [collapsedQuickSection]);
const collapsedRailItems = useMemo(() => {
const map = new Map<ToolId, ToolRegistryEntry>();
favoriteToolItems.forEach(({ id, tool }) => map.set(id, tool));
collapsedRecommendedItems.forEach(({ id, tool }) => {
if (!map.has(id)) map.set(id, tool);
});
return Array.from(map, ([id, tool]) => ({ id, tool }));
}, [favoriteToolItems, collapsedRecommendedItems]);
return (
<div
ref={toolPanelRef}
data-sidebar="tool-panel"
data-tour={fullscreenExpanded ? undefined : "tool-panel"}
className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${
isRainbowMode ? rainbowStyles.rainbowPaper : ""
} ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
style={{
width: computedWidth(),
padding: "0",
}}
>
{!fullscreenExpanded && !isPanelVisible && !isMobile && (
<div className="tool-panel__collapsed-strip">
<div className="tool-panel__collapsed-top">
<ActionIcon
variant="outline"
color="gray.4"
radius="xl"
size="md"
className="tool-panel__expand-btn"
onClick={handleExpand}
aria-label={t("toolPanel.expand", "Expand panel")}
>
<ChevronLeftIcon sx={{ fontSize: "1.1rem" }} />
</ActionIcon>
<AgentsCollapsedButton onExpand={handleExpand} />
</div>
<div className="tool-panel__collapsed-divider" />
<div className="tool-panel__collapsed-tools">
{collapsedRailItems.map(({ id, tool }) => (
<AppTooltip
key={id}
content={tool.name}
position="left"
arrow
delay={300}
>
<button
type="button"
className="tool-panel__collapsed-tool-btn"
data-selected={selectedToolKey === id}
onClick={() => {
handleExpand();
handleToolSelectWithTransition(id);
}}
aria-label={tool.name}
>
<ToolIcon icon={tool.icon} marginRight="0" />
</button>
</AppTooltip>
))}
</div>
</div>
)}
{!fullscreenExpanded && isPanelVisible && (
<div
/* Fixed width matches the expanded panel width so the inner content is
laid out at its final size from the moment it mounts. The outer
.tool-panel clips it (overflow-hidden) while it animates from the
collapsed 3.5rem width — text/icons stay put and just come into view
instead of jiggling as space becomes available. */
style={{
opacity: 1,
transition: "opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
height: "100%",
width: isMobile ? "100%" : "18.5rem",
flexShrink: 0,
display: "flex",
flexDirection: "column",
}}
>
<div className="tool-panel__compact-header">
{activeTool ? (
<div
className="tool-panel__active-tool-pill"
aria-label={activeTool.name}
>
<span className="tool-panel__active-tool-pill-icon">
<ToolIcon
icon={activeTool.icon}
marginRight="0"
color="var(--mantine-color-blue-filled)"
/>
</span>
<span className="tool-panel__active-tool-pill-label">
{activeTool.name}
</span>
</div>
) : showHeaderSearch ? (
<div className="tool-panel__compact-header-search">
<ToolSearch
value={searchQuery}
onChange={handleHeaderSearchChange}
toolRegistry={toolRegistry}
mode="filter"
autoFocus={allToolsView && !inToolView}
/>
</div>
) : (
showAgents && (
<span className="tool-panel__section-label">
{t("agents.section_title", "Agents")}
</span>
)
)}
{showCloseButton ? (
<ActionIcon
variant="subtle"
color="gray"
radius="xl"
size="md"
onClick={handleHeaderBack}
aria-label={
inToolView
? t("toolPanel.backToAllTools", "Back to all tools")
: t("toolPanel.goBack", "Go back")
}
className="tool-panel__expand-btn"
>
<CloseIcon sx={{ fontSize: "1.1rem" }} />
</ActionIcon>
) : (
<ActionIcon
variant="outline"
radius="xl"
size="md"
onClick={handleCollapse}
aria-label={t("toolPanel.collapse", "Collapse panel")}
className="tool-panel__expand-btn"
>
<ChevronRightIcon sx={{ fontSize: "1.1rem" }} />
</ActionIcon>
)}
</div>
{showAgents && <AgentsSection />}
<ToolPanel
allToolsView={allToolsView}
onShowAllTools={handleShowAllTools}
onToolSelect={handleToolSelectWithTransition}
compact={agentsEnabled && !allToolsView}
/>
<AgentsChatOverlay />
</div>
)}
<FullscreenToolPanel geometry={fullscreenGeometry} />
</div>
);
}
@@ -129,17 +129,70 @@
transition:
width 0.3s ease,
max-width 0.3s ease;
view-transition-name: tool-rail;
}
.tool-panel__collapsed-strip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
align-items: stretch;
padding-top: 10px;
gap: 8px;
height: 100%;
width: 100%;
min-height: 0;
}
.tool-panel__collapsed-top {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 0 0 10px;
}
.tool-panel__collapsed-divider {
height: 1px;
background: var(--border-subtle);
margin: 0 0.5rem 8px;
}
.tool-panel__collapsed-tools {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 0 0 12px;
overflow-y: auto;
overflow-x: hidden;
flex: 1 1 auto;
min-height: 0;
}
.tool-panel__collapsed-tool-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border: 1px solid transparent;
border-radius: 0.5rem;
background: transparent;
color: var(--tools-text-and-icon-color);
cursor: pointer;
transition:
background 120ms ease-out,
border-color 120ms ease-out;
padding: 0;
flex-shrink: 0;
}
.tool-panel__collapsed-tool-btn:hover {
background: var(--mantine-color-default-hover);
}
.tool-panel__collapsed-tool-btn[data-selected="true"] {
background: var(--mantine-color-default-hover);
border-color: var(--mantine-color-default-border);
}
.tool-panel__expand-btn {
@@ -174,6 +227,11 @@
flex-shrink: 0;
}
.tool-panel__back-bar {
flex-shrink: 0;
border-bottom: 1px solid var(--border-subtle) !important;
}
.tool-panel--fullscreen-active {
overflow: visible !important;
}
@@ -191,6 +249,133 @@
flex: 1 1 auto;
}
.tool-panel__compact-header {
display: flex;
align-items: center;
gap: 0.5rem;
min-height: 52px;
padding: 0.5rem 0.75rem;
box-sizing: border-box;
}
.tool-panel__compact-header .tool-panel__expand-btn {
margin-left: auto;
}
.tool-panel__compact-header-search {
flex: 1 1 auto;
min-width: 0;
}
.tool-panel__compact-header-search .search-input-container {
width: 100%;
}
.tool-panel__active-tool-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
flex: 1 1 auto;
min-width: 0;
padding: 0.35rem 0.75rem 0.35rem 0.4rem;
border: 1px solid var(--border-subtle);
border-radius: 9999px;
background: var(--mantine-color-body);
}
.tool-panel__active-tool-pill-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
border-radius: 9999px;
background: var(--mantine-color-blue-light);
color: var(--mantine-color-blue-filled);
flex-shrink: 0;
}
[data-mantine-color-scheme="dark"] .tool-panel__active-tool-pill {
background: transparent;
border-color: var(--border-subtle);
}
[data-mantine-color-scheme="dark"] .tool-panel__active-tool-pill-icon {
background: color-mix(
in srgb,
var(--mantine-color-blue-filled) 18%,
transparent
);
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
}
/* The inner .tool-button-icon wrapper carries its own transform/margin; reset
them so the icon glyph sits dead-centre in the circular background. */
.tool-panel__active-tool-pill-icon .tool-button-icon {
margin: 0 !important;
transform: none !important;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.tool-panel__active-tool-pill-icon svg {
font-size: 1rem;
width: 1rem;
height: 1rem;
}
.tool-panel__active-tool-pill-label {
font-size: 0.9rem;
font-weight: 600;
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
}
::view-transition-old(tool-rail) {
animation: vt-rail-fade-out 180ms ease-out forwards;
}
::view-transition-new(tool-rail) {
animation: vt-rail-fade-in 220ms ease-in forwards;
}
@keyframes vt-rail-fade-out {
to {
opacity: 0;
}
}
@keyframes vt-rail-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(tool-rail),
::view-transition-new(tool-rail) {
animation-duration: 1ms !important;
}
}
.tool-panel__section-label {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
line-height: 1;
}
.tool-panel__mode-toggle {
transition: transform 0.2s ease;
}
@@ -652,6 +837,33 @@
gap: 0.75rem;
}
/* Agents card lives in its own column above the tool grid. It uses the same
.tool-panel__fullscreen-group base styles but with a colourful gradient
border so it stands out as a distinct surface (and doesn't blend into the
neighbouring category cards). */
.tool-panel__fullscreen-agents {
padding: 1.5rem 1.75rem 0;
}
.tool-panel__fullscreen-group--agents {
position: relative;
background:
linear-gradient(var(--fullscreen-bg-group), var(--fullscreen-bg-group))
padding-box,
linear-gradient(
135deg,
var(--mantine-color-blue-6) 0%,
var(--mantine-color-violet-6) 45%,
var(--mantine-color-pink-6) 100%
)
border-box;
border: 1.5px solid transparent;
}
.tool-panel__fullscreen-section-icon--agents {
color: var(--mantine-color-blue-6);
}
@keyframes tool-panel-fullscreen-slide-in {
from {
transform: translateX(6%) scaleX(0.85);
@@ -1,303 +1,85 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider";
import {
useToolWorkflow,
useToolWorkflowActions,
} from "@app/contexts/ToolWorkflowContext";
import { usePreferences } from "@app/contexts/PreferencesContext";
import { ScrollArea } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import ToolPicker from "@app/components/tools/ToolPicker";
import SearchResults from "@app/components/tools/SearchResults";
import ToolRenderer from "@app/components/tools/ToolRenderer";
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
import { useSidebarContext } from "@app/contexts/SidebarContext";
import rainbowStyles from "@app/styles/rainbow.module.css";
import { ActionIcon, Button, ScrollArea } from "@mantine/core";
import { ToolId } from "@app/types/toolId";
import { useIsMobile } from "@app/hooks/useIsMobile";
import { useTranslation } from "react-i18next";
import FullscreenToolSurface from "@app/components/tools/FullscreenToolSurface";
import { ToolPanelViewerBar } from "@app/components/tools/ToolPanelViewerBar";
import { useToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
import { useWorkbenchBar } from "@app/contexts/WorkbenchBarContext";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import SearchIcon from "@mui/icons-material/Search";
import "@app/components/tools/ToolPanel.css";
import { ToolId } from "@app/types/toolId";
// No props needed - component uses context
interface ToolPanelProps {
/** Whether to expand into the full categorised tools view (with search). */
allToolsView: boolean;
/** Trigger to enter the all-tools view (from the "View all tools" button). */
onShowAllTools: () => void;
/**
* Tool-selection handler injected by {@code RightSidebar} so the click can
* be wrapped in a View Transition (the tool card morphs into the header
* pill). Falls back to the workflow context's handler when not provided.
*/
onToolSelect?: (id: ToolId) => void;
/** Whether to render the compact (favourites + recommended only) view. */
compact?: boolean;
}
export default function ToolPanel() {
/** Tool list and renderer for the right rail; rail chrome lives in RightSidebar. */
export default function ToolPanel({
allToolsView,
onShowAllTools,
onToolSelect,
compact: compactProp,
}: ToolPanelProps) {
const { t } = useTranslation();
const { isRainbowMode } = useRainbowThemeContext();
const { sidebarRefs } = useSidebarContext();
const { toolPanelRef, quickAccessRef } = sidebarRefs;
const isMobile = useIsMobile();
const {
leftPanelView,
isPanelVisible,
searchQuery,
filteredTools,
toolRegistry,
setSearchQuery,
selectedToolKey,
toolPanelMode,
sidebarsVisible,
readerMode,
} = useToolWorkflow();
const {
handleToolSelect,
handleBackToTools,
setPreviewFile,
setToolPanelMode,
setLeftPanelView,
setReaderMode,
setSidebarsVisible,
} = useToolWorkflowActions();
const { setAllButtonsDisabled } = useWorkbenchBar();
const { preferences, updatePreference } = usePreferences();
const isFullscreenMode = toolPanelMode === "fullscreen";
const toolPickerVisible = !readerMode;
const fullscreenExpanded =
isFullscreenMode &&
leftPanelView === "toolPicker" &&
!isMobile &&
toolPickerVisible;
// Disable workbench bar buttons when fullscreen mode is active
useEffect(() => {
setAllButtonsDisabled(fullscreenExpanded);
}, [fullscreenExpanded, setAllButtonsDisabled]);
const fullscreenGeometry = useToolPanelGeometry({
enabled: fullscreenExpanded,
toolPanelRef,
quickAccessRef,
});
const handleExpand = () => {
if (readerMode) setReaderMode(false);
if (leftPanelView === "hidden") setLeftPanelView("toolPicker");
if (!sidebarsVisible) setSidebarsVisible(true);
};
const handleCollapse = () => {
setLeftPanelView("hidden");
};
const [focusSearch, setFocusSearch] = useState(false);
const focusSearchOnNextOpen = useRef(false);
const handleExpandAndSearch = () => {
focusSearchOnNextOpen.current = true;
handleExpand();
};
// Once the panel becomes visible, consume the focus-search request
useEffect(() => {
if (isPanelVisible && focusSearchOnNextOpen.current) {
focusSearchOnNextOpen.current = false;
setFocusSearch(true);
// Reset after one render so autoFocus doesn't re-fire on subsequent renders
const id = setTimeout(() => setFocusSearch(false), 100);
return () => clearTimeout(id);
}
}, [isPanelVisible]);
const computedWidth = () => {
if (isMobile) {
return "100%";
}
if (!isPanelVisible) {
return "3.5rem";
}
return "18.5rem";
};
const handleSelect = useCallback(
(id: string) => handleToolSelect(id as ToolId),
[handleToolSelect],
);
const matchedTextMap = useMemo(() => {
const map = new Map<string, string>();
filteredTools.forEach(({ item: [id], matchedText }) => {
if (matchedText) {
map.set(id, matchedText);
}
});
return map;
}, [filteredTools]);
} = useToolWorkflow();
const selectTool = onToolSelect ?? handleToolSelect;
return (
<div
ref={toolPanelRef}
data-sidebar="tool-panel"
data-tour={fullscreenExpanded ? undefined : "tool-panel"}
className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${
isRainbowMode ? rainbowStyles.rainbowPaper : ""
} ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
style={{
width: computedWidth(),
padding: "0",
}}
>
{!fullscreenExpanded && !isPanelVisible && !isMobile && (
<div className="tool-panel__collapsed-strip">
<ActionIcon
variant="outline"
color="gray.4"
radius="xl"
size="md"
className="tool-panel__expand-btn"
onClick={handleExpand}
aria-label={t("toolPanel.expand", "Expand panel")}
>
<ChevronLeftIcon sx={{ fontSize: "1.1rem" }} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="gray"
radius="md"
size="md"
className="tool-panel__collapsed-search-btn"
onClick={handleExpandAndSearch}
aria-label={t("toolPanel.search", "Search tools")}
style={{ marginTop: "8px" }}
>
<SearchIcon sx={{ fontSize: "1.25rem" }} />
</ActionIcon>
<>
{/* Viewer mode tools — annotate, redact, form fill */}
<ToolPanelViewerBar />
{allToolsView && searchQuery.trim().length > 0 ? (
<div className="flex-1 flex flex-col overflow-y-auto">
<SearchResults
filteredTools={filteredTools}
onSelect={(id) => selectTool(id as ToolId)}
searchQuery={searchQuery}
/>
</div>
)}
{!fullscreenExpanded && isPanelVisible && (
<div
style={{
opacity: 1,
transition: "opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
height: "100%",
display: "flex",
flexDirection: "column",
}}
>
{/* Viewer mode tools — annotate, redact, form fill */}
<ToolPanelViewerBar />
<div
className="tool-panel__search-row"
style={{
backgroundColor: "transparent",
borderBottom: "1px solid var(--border-subtle)",
}}
>
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
autoFocus={focusSearch}
/>
<ActionIcon
variant="outline"
radius="xl"
size="md"
onClick={handleCollapse}
aria-label={t("toolPanel.collapse", "Collapse panel")}
className="tool-panel__expand-btn"
style={{ flexShrink: 0 }}
>
<ChevronRightIcon sx={{ fontSize: "1.1rem" }} />
</ActionIcon>
</div>
{searchQuery.trim().length > 0 ? (
<div className="flex-1 flex flex-col overflow-y-auto">
<SearchResults
filteredTools={filteredTools}
onSelect={handleSelect}
searchQuery={searchQuery}
/>
</div>
) : leftPanelView === "toolPicker" ? (
<div className="flex-1 flex flex-col overflow-auto">
<ToolPicker
) : leftPanelView === "toolPicker" ? (
<div className="flex-1 flex flex-col overflow-auto">
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => selectTool(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
compact={compactProp ?? !allToolsView}
onShowAllTools={onShowAllTools}
/>
</div>
) : (
<div className="flex-1 min-h-0 overflow-hidden">
<ScrollArea h="100%">
{selectedToolKey ? (
<ToolRenderer
selectedToolKey={selectedToolKey}
onSelect={handleSelect}
filteredTools={filteredTools}
isSearching={Boolean(
searchQuery && searchQuery.trim().length > 0,
)}
onPreviewFile={setPreviewFile}
/>
</div>
) : (
<div className="flex-1 flex flex-col overflow-hidden">
<div
style={{
borderBottom: "1px solid var(--border-subtle)",
flexShrink: 0,
}}
>
<Button
variant="light"
color="blue"
size="sm"
fullWidth
radius={0}
leftSection={<ArrowBackIcon sx={{ fontSize: "0.9rem" }} />}
onClick={handleBackToTools}
aria-label={t("toolPanel.backToTools", "Back to tools")}
styles={{ root: { justifyContent: "flex-start" } }}
>
{t("toolPanel.backToTools", "Back to tools")}
</Button>
) : (
<div className="tool-panel__placeholder">
{t("toolPanel.placeholder", "Choose a tool to get started")}
</div>
<div className="flex-1 min-h-0 overflow-hidden">
<ScrollArea h="100%">
{selectedToolKey ? (
<ToolRenderer
selectedToolKey={selectedToolKey}
onPreviewFile={setPreviewFile}
/>
) : (
<div className="tool-panel__placeholder">
{t(
"toolPanel.placeholder",
"Choose a tool to get started",
)}
</div>
)}
</ScrollArea>
</div>
</div>
)}
)}
</ScrollArea>
</div>
)}
{fullscreenExpanded && (
<FullscreenToolSurface
searchQuery={searchQuery}
toolRegistry={toolRegistry}
filteredTools={filteredTools}
selectedToolKey={selectedToolKey}
showDescriptions={preferences.showLegacyToolDescriptions}
matchedTextMap={matchedTextMap}
onSearchChange={setSearchQuery}
onSelect={(id: ToolId) => handleToolSelect(id)}
onToggleDescriptions={() =>
updatePreference(
"showLegacyToolDescriptions",
!preferences.showLegacyToolDescriptions,
)
}
onExitFullscreenMode={() => setToolPanelMode("sidebar")}
geometry={fullscreenGeometry}
/>
)}
</div>
</>
);
}
@@ -1,5 +1,5 @@
import React, { memo, useMemo, useRef } from "react";
import { Box, Stack } from "@mantine/core";
import React, { useMemo, useRef } from "react";
import { Box, Button, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
import "@app/components/tools/toolPicker/ToolPicker.css";
@@ -22,6 +22,10 @@ interface ToolPickerProps {
matchedText?: string;
}>;
isSearching?: boolean;
/** Compact "resting" view: favourites + recommended only, with a button to expand. */
compact?: boolean;
/** Called when the user clicks "View all tools" in compact mode. */
onShowAllTools?: () => void;
}
const EMPTY_FILTERED_TOOLS: ToolPickerProps["filteredTools"] = [];
@@ -57,6 +61,8 @@ const ToolPicker = ({
onSelect,
filteredTools,
isSearching = false,
compact = false,
onShowAllTools,
}: ToolPickerProps) => {
const { t } = useTranslation();
@@ -118,9 +124,60 @@ const ToolPicker = ({
)
)}
</Stack>
) : compact ? (
/* Resting state: flat list of pinned + recommended only. */
<Box className="tool-picker__compact">
<div style={HEADER_TEXT_STYLE}>
{t("toolPanel.toolsHeader", "Tools")}
</div>
{favoriteToolItems.length === 0 && recommendedItems.length === 0 ? (
<NoToolsFound />
) : (
<div className="tool-picker__compact-list">
{favoriteToolItems.map(({ id, tool }) => (
<ToolButton
key={`fav-${id}`}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
hasStars
showDescription
/>
))}
{recommendedItems
.filter(
({ id }) => !favoriteToolItems.some((fav) => fav.id === id),
)
.map(({ id, tool }) => (
<ToolButton
key={`rec-${id}`}
id={id as ToolId}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
hasStars
showDescription
/>
))}
</div>
)}
{onShowAllTools && (
<Button
variant="subtle"
size="sm"
fullWidth
onClick={onShowAllTools}
className="tool-picker__view-all"
aria-label={t("toolPanel.viewAllTools", "View all tools")}
>
{t("toolPanel.viewAllTools", "View all tools")}
</Button>
)}
</Box>
) : (
<>
{/* Flat list: favorites and recommended first, then all subcategories */}
{/* All-tools view: favourites + recommended + all subcategories. */}
<Stack p="sm" gap="xs">
{favoriteToolItems.length > 0 && (
<Box w="100%">
@@ -182,7 +239,6 @@ const ToolPicker = ({
{!quickSection && !allSection && <NoToolsFound />}
{/* bottom spacer to allow scrolling past the last row */}
<div aria-hidden style={{ height: 200 }} />
</>
)}
@@ -192,4 +248,4 @@ const ToolPicker = ({
);
};
export default memo(ToolPicker);
export default ToolPicker;
@@ -32,6 +32,7 @@ interface ToolButtonProps {
disableNavigation?: boolean;
matchedSynonym?: string;
hasStars?: boolean;
showDescription?: boolean;
/** Called when an unavailable tool is clicked; if provided, overrides the default no-op */
onUnavailableClick?: () => void;
}
@@ -44,6 +45,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
disableNavigation = false,
matchedSynonym,
hasStars = false,
showDescription = false,
onUnavailableClick,
}) => {
const { t } = useTranslation();
@@ -183,6 +185,14 @@ const ToolButton: React.FC<ToolButtonProps> = ({
)}
{usesCloud && !visuallyUnavailable && <CloudBadge />}
</div>
{showDescription && tool.description && (
<span
className="tool-button__description"
style={{ opacity: visuallyUnavailable ? 0.25 : 1 }}
>
{tool.description}
</span>
)}
{matchedSynonym && (
<span
style={{
@@ -205,8 +215,8 @@ const ToolButton: React.FC<ToolButtonProps> = ({
handleUnlessSpecialClick(e, () => handleClick(id));
};
const selectedStyles = isSelected
? { backgroundColor: "#EAEAEA", color: "var(--tools-text-and-icon-color)" }
const selectedBg = isSelected
? { backgroundColor: "var(--tool-button-selected-bg)" }
: {};
const buttonElement = navProps ? (
@@ -227,7 +237,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
overflow: "visible",
...selectedStyles,
...selectedBg,
},
label: { overflow: "visible" },
}}
@@ -254,7 +264,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
overflow: "visible",
...selectedStyles,
...selectedBg,
},
label: { overflow: "visible" },
}}
@@ -279,7 +289,6 @@ const ToolButton: React.FC<ToolButtonProps> = ({
color: "var(--tools-text-and-icon-color)",
cursor: visuallyUnavailable ? "not-allowed" : undefined,
overflow: "visible",
...selectedStyles,
},
label: { overflow: "visible" },
}}
@@ -55,6 +55,15 @@
flex: 1 1 auto;
}
/* Selected tool highlight — theme-aware via CSS variable */
:root {
--tool-button-selected-bg: var(--mantine-color-gray-2);
}
[data-mantine-color-scheme="dark"] {
--tool-button-selected-bg: var(--mantine-color-dark-4);
}
/* Compact tool buttons */
.tool-button {
font-size: 0.875rem;
@@ -96,3 +105,37 @@
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.tool-button__description {
font-size: 0.75rem;
color: var(--mantine-color-dimmed);
line-height: 1.35;
margin-top: 0.15rem;
white-space: normal;
text-align: left;
}
/* Resting / compact tool list — taller rows, room for description. */
.tool-picker__compact {
padding: 0.5rem var(--mantine-spacing-sm) var(--mantine-spacing-sm);
}
.tool-picker__compact .tool-button {
padding-top: 0.65rem;
padding-bottom: 0.65rem;
height: auto;
}
.tool-picker__compact .tool-button .mantine-Button-label {
align-items: center;
}
.tool-picker__compact-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.tool-picker__view-all {
margin-top: 0.75rem;
}
@@ -17,17 +17,13 @@ function CopyButton({ text }: { text: string }) {
position: "absolute",
top: 8,
right: 8,
background: copied
? "var(--mantine-color-green-0)"
: "var(--mantine-color-gray-0)",
border: "1px solid var(--mantine-color-gray-3)",
background: copied ? "var(--color-green-100)" : "var(--bg-muted)",
border: "1px solid var(--border-default)",
borderRadius: 3,
cursor: "pointer",
fontSize: "0.7em",
padding: "2px 8px",
color: copied
? "var(--mantine-color-green-7)"
: "var(--mantine-color-gray-7)",
color: copied ? "var(--color-green-700)" : "var(--text-secondary)",
}}
>
{copied ? "✓ Copied" : "Copy"}
@@ -36,6 +32,183 @@ function CopyButton({ text }: { text: string }) {
}
const components: Components = {
h1: ({ children }) => (
<h1
style={{
color: "var(--text-primary)",
borderBottom: "1px solid var(--border-default)",
paddingBottom: "0.3em",
marginTop: "1.2em",
marginBottom: "0.6em",
}}
>
{children}
</h1>
),
h2: ({ children }) => (
<h2
style={{
color: "var(--text-primary)",
borderBottom: "1px solid var(--border-subtle)",
paddingBottom: "0.2em",
marginTop: "1.1em",
marginBottom: "0.5em",
}}
>
{children}
</h2>
),
h3: ({ children }) => (
<h3
style={{
color: "var(--text-primary)",
marginTop: "1em",
marginBottom: "0.4em",
}}
>
{children}
</h3>
),
h4: ({ children }) => (
<h4
style={{
color: "var(--text-primary)",
marginTop: "0.8em",
marginBottom: "0.3em",
}}
>
{children}
</h4>
),
h5: ({ children }) => (
<h5
style={{
color: "var(--text-secondary)",
marginTop: "0.8em",
marginBottom: "0.3em",
}}
>
{children}
</h5>
),
h6: ({ children }) => (
<h6
style={{
color: "var(--text-muted)",
marginTop: "0.8em",
marginBottom: "0.3em",
}}
>
{children}
</h6>
),
p: ({ children }) => (
<p
style={{
color: "var(--text-primary)",
lineHeight: 1.7,
marginBottom: "0.75em",
}}
>
{children}
</p>
),
a: ({ children, href }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
style={{
color: "var(--accent-interactive)",
textDecoration: "underline",
}}
>
{children}
</a>
),
strong: ({ children }) => (
<strong style={{ color: "var(--text-primary)", fontWeight: 600 }}>
{children}
</strong>
),
em: ({ children }) => (
<em style={{ color: "var(--text-secondary)" }}>{children}</em>
),
blockquote: ({ children }) => (
<blockquote
style={{
borderLeft: "3px solid var(--border-strong)",
margin: "0.75em 0",
padding: "4px 16px",
color: "var(--text-secondary)",
background: "var(--bg-muted)",
borderRadius: "0 4px 4px 0",
}}
>
{children}
</blockquote>
),
hr: () => (
<hr
style={{
border: "none",
borderTop: "1px solid var(--border-default)",
margin: "1.2em 0",
}}
/>
),
ul: ({ children }) => (
<ul
style={{
color: "var(--text-primary)",
paddingLeft: "1.5em",
marginBottom: "0.75em",
}}
>
{children}
</ul>
),
ol: ({ children }) => (
<ol
style={{
color: "var(--text-primary)",
paddingLeft: "1.5em",
marginBottom: "0.75em",
}}
>
{children}
</ol>
),
li: ({ children }) => (
<li
style={{
color: "var(--text-primary)",
lineHeight: 1.6,
marginBottom: "0.2em",
}}
>
{children}
</li>
),
code: ({ children, className }) => {
const isBlock = className?.startsWith("language-");
if (isBlock) return <code className={className}>{children}</code>;
return (
<code
style={{
background: "var(--bg-muted)",
color: "var(--code-kw-color)",
border: "1px solid var(--border-subtle)",
borderRadius: 3,
padding: "1px 5px",
fontSize: "0.85em",
fontFamily: "monospace",
}}
>
{children}
</code>
);
},
pre: ({ children }) => {
const codeText = React.isValidElement(children)
? String((children.props as { children?: unknown }).children ?? "")
@@ -44,12 +217,15 @@ const components: Components = {
<div style={{ position: "relative", margin: "8px 0" }}>
<pre
style={{
background: "var(--mantine-color-gray-1)",
background: "var(--bg-muted)",
color: "var(--text-primary)",
border: "1px solid var(--border-default)",
padding: "10px 52px 10px 14px",
borderRadius: 4,
overflowX: "auto",
fontSize: "0.85em",
margin: 0,
fontFamily: "monospace",
}}
>
{children}
@@ -65,18 +241,33 @@ const components: Components = {
borderCollapse: "collapse",
width: "100%",
fontSize: "0.85em",
color: "var(--text-primary)",
}}
>
{children}
</table>
</div>
),
thead: ({ children }) => (
<thead style={{ background: "var(--bg-muted)" }}>{children}</thead>
),
tbody: ({ children }) => <tbody>{children}</tbody>,
tr: ({ children }) => (
<tr
style={{
borderBottom: "1px solid var(--border-default)",
}}
>
{children}
</tr>
),
th: ({ children, style }) => (
<th
style={{
border: "1px solid var(--mantine-color-gray-3)",
border: "1px solid var(--border-default)",
padding: "6px 10px",
background: "var(--mantine-color-gray-1)",
background: "var(--bg-raised)",
color: "var(--text-primary)",
textAlign: "left",
fontWeight: 600,
whiteSpace: "nowrap",
@@ -89,8 +280,9 @@ const components: Components = {
td: ({ children, style }) => (
<td
style={{
border: "1px solid var(--mantine-color-gray-3)",
border: "1px solid var(--border-default)",
padding: "5px 10px",
color: "var(--text-primary)",
...style,
}}
>
@@ -92,8 +92,10 @@ export function TextViewer({ file, isMarkdown }: TextViewerProps) {
maxWidth: 800,
margin: "0 auto",
padding: "20px 28px",
background: "#ffffff",
background: "var(--bg-surface)",
color: "var(--text-primary)",
borderRadius: 6,
border: "1px solid var(--border-subtle)",
}}
>
{renderedMarkdown}
+1 -1
View File
@@ -1,6 +1,6 @@
// Theme constants and utilities
export type ThemeMode = "light" | "dark" | "rainbow";
export type ThemeMode = "light" | "dark" | "midnight" | "rainbow";
// Detect OS theme preference
export function getSystemTheme(): "light" | "dark" {
@@ -1,8 +1,6 @@
import { PageEditorFunctions } from "@app/types/pageEditor";
import {
type ToolPanelMode,
DEFAULT_TOOL_PANEL_MODE,
} from "@app/constants/toolPanel";
import { type ToolPanelMode } from "@app/constants/toolPanel";
import { preferencesService } from "@app/services/preferencesService";
export interface ToolWorkflowState {
// UI State
@@ -43,7 +41,7 @@ export const baseState: Omit<ToolWorkflowState, "toolPanelMode"> = {
export const createInitialState = (): ToolWorkflowState => ({
...baseState,
toolPanelMode: DEFAULT_TOOL_PANEL_MODE,
toolPanelMode: preferencesService.getPreference("defaultToolPanelMode"),
});
export function toolWorkflowReducer(
@@ -13,6 +13,31 @@ interface UseToolPanelGeometryOptions {
quickAccessRef: RefObject<HTMLDivElement | null>;
}
function computeGeometry(
panelEl: HTMLDivElement,
quickAccessRef: RefObject<HTMLDivElement | null>,
): ToolPanelGeometry {
const rect = panelEl.getBoundingClientRect();
const isRTL =
typeof document !== "undefined" && document.documentElement.dir === "rtl";
let width: number;
let left: number;
if (isRTL) {
// RTL: panel is on the left, expands rightward
width = Math.max(360, window.innerWidth - rect.right);
left = rect.right;
} else {
// LTR: panel is on the right, expands leftward to the file sidebar
const quickAccessRect = quickAccessRef.current?.getBoundingClientRect();
const leftOffset = quickAccessRect ? quickAccessRect.right : 0;
width = Math.max(360, rect.right - leftOffset);
left = leftOffset;
}
const height = Math.max(rect.height, window.innerHeight - rect.top);
return { left, top: rect.top, width, height };
}
export function useToolPanelGeometry({
enabled,
toolPanelRef,
@@ -36,31 +61,7 @@ export function useToolPanelGeometry({
let rafId: number | null = null;
const computeAndSetGeometry = () => {
const rect = panelEl.getBoundingClientRect();
const isRTL =
typeof document !== "undefined" &&
document.documentElement.dir === "rtl";
let width: number;
let left: number;
if (isRTL) {
// RTL: panel is on the left, expands rightward
width = Math.max(360, window.innerWidth - rect.right);
left = rect.right;
} else {
// LTR: panel is on the right, expands leftward to the file sidebar
const quickAccessRect = quickAccessRef.current?.getBoundingClientRect();
const leftOffset = quickAccessRect ? quickAccessRect.right : 0;
width = Math.max(360, rect.right - leftOffset);
left = leftOffset;
}
const height = Math.max(rect.height, window.innerHeight - rect.top);
setGeometry({
left,
top: rect.top,
width,
height,
});
setGeometry(computeGeometry(panelEl, quickAccessRef));
};
const scheduleUpdate = () => {
@@ -175,9 +175,13 @@ export function useRainbowTheme(): RainbowThemeHook {
return;
}
// Normal theme switching
const nextTheme = themeMode === "light" ? "dark" : "light";
updatePreference("theme", nextTheme);
// Normal theme switching: light → dark → midnight → light
const cycle: Record<string, ThemeMode> = {
light: "dark",
dark: "midnight",
midnight: "light",
};
updatePreference("theme", cycle[themeMode] ?? "light");
}, [themeMode, updatePreference]);
const activateRainbow = useCallback(() => {
+3 -3
View File
@@ -18,7 +18,7 @@ import { useViewer } from "@app/contexts/ViewerContext";
import { useLocation } from "react-router-dom";
import AppsIcon from "@mui/icons-material/AppsRounded";
import ToolPanel from "@app/components/tools/ToolPanel";
import RightSidebar from "@app/components/tools/RightSidebar";
import Workbench from "@app/components/layout/Workbench";
import FileSidebar from "@app/components/shared/FileSidebar";
import FileManager from "@app/components/FileManager";
@@ -290,7 +290,7 @@ export default function HomePage() {
aria-label={t("home.mobile.toolsSlide", "Tool selection panel")}
>
<div className="mobile-slide-content">
<ToolPanel />
<RightSidebar />
</div>
</div>
<div
@@ -381,7 +381,7 @@ export default function HomePage() {
onOpenSettings={() => setConfigModalOpen(true)}
/>
<Workbench />
{!hideToolPanel && <ToolPanel />}
{!hideToolPanel && <RightSidebar />}
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
<AppConfigModal
opened={configModalOpen}
+328 -219
View File
@@ -14,12 +14,6 @@
--spdf-compare-inline-added-bg: rgba(52, 199, 89, 0.25);
/* File Manager active file colors */
--file-active-bg: rgba(
59,
130,
246,
0.1
); /* Transparent blue for active file background */
--file-active-badge-bg: rgba(
34,
197,
@@ -43,9 +37,7 @@
--fullscreen-anim-duration-out: 0.22s;
/* Z-index constants (added in this PR) */
--z-over-fullscreen-surface: 1400;
--z-fullscreen-surface: 1200;
--z-fullscreen-favorite-star: 2;
--z-fullscreen-icon-svg: 1;
--z-toolpicker-star: 1;
@@ -162,10 +154,8 @@
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
/* Font weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Light theme semantic colors */
--bg-surface: #ffffff;
@@ -192,22 +182,9 @@
/* Icon colors for light mode */
--icon-user-bg: #9ca3af;
--icon-user-color: #ffffff;
--icon-notifications-bg: #9ca3af;
--icon-notifications-color: #ffffff;
--icon-tools-bg: #1e88e5;
--icon-tools-color: #ffffff;
--icon-read-bg: #4caf50;
--icon-read-color: #ffffff;
--icon-sign-bg: #3ba99c;
--icon-sign-color: #ffffff;
--icon-automate-bg: #a576e3;
--icon-automate-color: #ffffff;
--icon-files-bg: #d3e7f7;
--icon-files-color: #0a8bff;
--icon-activity-bg: #d3e7f7;
--icon-activity-color: #0a8bff;
--icon-config-bg: #9ca3af;
--icon-config-color: #ffffff;
/* Colors for tooltips */
--tooltip-title-bg: #dbefff;
@@ -224,7 +201,6 @@
--tools-text-and-icon-color: var(--text-primary);
/* Tool picker sticky header variables (light mode) */
--tool-header-bg: #dbefff;
--tool-header-border: #bee2ff;
--tool-header-text: #1e88e5;
--tool-header-badge-bg: #c0ddff;
@@ -235,7 +211,6 @@
--tool-subcategory-rule-color: #e5e7eb; /* doubly lighter rule line */
--accent-interactive: #4a90e2;
--text-instruction: #4a90e2;
--text-brand: var(--color-gray-700);
--text-brand-accent: #dc2626;
/* PDF text selection colors */
@@ -251,16 +226,13 @@
--tool-panel-search-border-bottom: #eff1f4;
/* container */
--landing-paper-bg: var(--bg-surface);
--landing-inner-paper-bg: #eef8ff;
--landing-inner-paper-border: #cdeaff;
--landing-button-bg: var(--bg-surface);
--landing-button-color: var(--icon-tools-bg);
--landing-button-border: #e0f2f7;
--landing-button-hover-bg: rgb(251, 251, 251);
/* drop state */
--landing-drop-paper-bg: #e3f2fd;
--landing-drop-inner-paper-bg: #bbdefb;
--landing-drop-inner-paper-border: #90caf9;
@@ -268,36 +240,15 @@
--landing-hero-gradient: linear-gradient(135deg, #4c8bf5 0%, #3a7be8 100%);
--landing-stack-w: 224px;
--landing-stack-h: 176px;
--landing-stack-glow-bg: radial-gradient(
circle,
rgba(74, 144, 226, 0.18) 0%,
transparent 70%
);
/* landing doc stack shadows */
--landing-doc-shadow-back-idle:
0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04);
--landing-doc-shadow-back-hover:
0 12px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
--landing-doc-shadow-front-idle:
0 8px 30px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(0, 0, 0, 0.02);
--landing-doc-shadow-front-hover:
0 18px 48px rgba(0, 0, 0, 0.18), 0 8px 20px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(0, 0, 0, 0.04);
/* landing action button shadows */
--landing-action-transition:
transform 0.28s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.32s cubic-bezier(0.4, 0, 0.2, 1);
--landing-action-shadow-idle:
0 6px 18px rgba(0, 0, 0, 0), 0 2px 6px rgba(0, 0, 0, 0);
--landing-action-shadow-hover:
0 6px 18px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
--landing-action-primary-shadow-idle:
0 10px 26px rgba(58, 123, 232, 0), 0 4px 12px rgba(0, 0, 0, 0);
--landing-action-primary-shadow-hover:
0 10px 26px rgba(58, 123, 232, 0.42), 0 4px 12px rgba(0, 0, 0, 0.1);
/* selected file header colors */
--header-selected-bg: #1e88e5; /* light mode selected header matches dark */
@@ -306,18 +257,10 @@
--accordion-item-bg: #e8eaed; /* accordion item background - more distinguishable */
/* shadows */
--drop-shadow-color: rgba(0, 0, 0, 0.08);
--drop-shadow-color-strong: rgba(0, 0, 0, 0.04);
--drop-shadow-filter: drop-shadow(0 0.2rem 0.4rem rgba(0, 0, 0, 0.08))
drop-shadow(0 0.6rem 0.6rem rgba(0, 0, 0, 0.06))
drop-shadow(0 1.2rem 1rem rgba(0, 0, 0, 0.04));
/* Light mode card hover and selection */
--header-hover-bg: #3b4b6e; /* same family as selected, a touch muted for hover */
--card-selected-border: #3fafff; /* slightly more blue than dark mode header */
--checkbox-border: #2f83bf;
--checkbox-checked-bg: #3fafff;
--checkbox-tick: #ffffff;
--information-text-bg: #eaeaea;
--information-text-color: #5e5e5e;
@@ -348,15 +291,9 @@
--api-keys-button-color: #333333;
/* PDF Report Colors (always light) */
--pdf-light-header-bg: 239 246 255;
--pdf-light-accent: 59 130 246;
--pdf-light-text-primary: 30 41 59;
--pdf-light-text-muted: 100 116 139;
--pdf-light-box-bg: 248 250 252;
--pdf-light-box-border: 226 232 240;
--pdf-light-warning: 234 179 8;
--pdf-light-danger: 248 113 113;
--pdf-light-success: 34 197 94;
--pdf-light-neutral: 148 163 184;
--pdf-light-status-valid-bg: 209 250 229;
--pdf-light-status-valid-text: 6 95 70;
@@ -366,8 +303,6 @@
--pdf-light-status-invalid-text: 153 27 27;
--pdf-light-status-neutral-bg: 229 231 235;
--pdf-light-status-neutral-text: 55 65 81;
--pdf-light-report-container-bg: 249 250 251;
--pdf-light-simulated-page-bg: 255 255 255;
--pdf-light-simulated-page-text: 15 23 42;
/* Code token colors (light mode) */
@@ -376,11 +311,6 @@
--code-num-color: #4338ca; /* indigo-700 */
--code-com-color: #6b7280; /* gray-500 */
/* Compare tool specific colors - only for colors that don't have existing theme pairs */
--compare-upload-dropzone-bg: rgba(241, 245, 249, 0.45);
--compare-upload-dropzone-border: rgba(148, 163, 184, 0.6);
--compare-upload-icon-bg: rgba(148, 163, 184, 0.2);
--compare-upload-icon-color: rgba(17, 24, 39, 0.75);
--compare-upload-divider: rgba(148, 163, 184, 0.5);
/* Compare page label chip (light mode): slightly lighter than surrounding rows */
--compare-page-label-bg: var(--bg-muted);
@@ -420,12 +350,12 @@
}
[data-mantine-color-scheme="dark"] {
/* Dark theme gray scale (inverted) */
--gray-50: 17 24 39;
--gray-100: 31 35 41;
--gray-200: 42 47 54;
--gray-300: 55 65 81;
--gray-400: 75 85 99;
/* Dark theme gray scale — very dark, near-black */
--gray-50: 7 10 14;
--gray-100: 10 13 18;
--gray-200: 15 19 24;
--gray-300: 22 28 36;
--gray-400: 36 44 58;
--gray-500: 107 114 128;
--gray-600: 156 163 175;
--gray-700: 209 213 219;
@@ -433,9 +363,9 @@
--gray-900: 243 244 246;
/* Dark semantic colors for Tailwind */
--surface: 31 35 41;
--background: 42 47 54;
--border: 55 65 81;
--surface: 15 19 24;
--background: 10 13 18;
--border: 22 28 36;
/* Dark theme Mantine colors */
--color-red-50: #2d1b1b;
@@ -449,11 +379,11 @@
--color-red-800: #991b1b;
--color-red-900: #7f1d1d;
--color-gray-50: #111827;
--color-gray-100: #1f2329;
--color-gray-200: #2a2f36;
--color-gray-300: #374151;
--color-gray-400: #4b5563;
--color-gray-50: #070a0e;
--color-gray-100: #0a0d12;
--color-gray-200: #0f1318;
--color-gray-300: #161c24;
--color-gray-400: #242c3a;
--color-gray-500: #6b7280;
--color-gray-600: #9ca3af;
--color-gray-700: #d1d5db;
@@ -461,20 +391,20 @@
--color-gray-900: #f3f4f6;
/* Category colors - same as light mode for consistency */
--category-color-removal: #ef4444; /* Red for removal tools */
--category-color-security: #f59e0b; /* Orange for security tools */
--category-color-formatting: #8b5cf6; /* Purple for formatting tools */
--category-color-extraction: #06b6d4; /* Cyan for extraction tools */
--category-color-signing: #10b981; /* Green for signing tools */
--category-color-general: #3b82f6; /* Blue for general tools */
--category-color-verification: #f97316; /* Orange for verification tools */
--category-color-automation: #ec4899; /* Pink for automation tools */
--category-color-developer: #6b7280; /* Gray for developer tools */
--category-color-default: #6b7280; /* Default gray */
--category-color-removal: #ef4444;
--category-color-security: #f59e0b;
--category-color-formatting: #8b5cf6;
--category-color-extraction: #06b6d4;
--category-color-signing: #10b981;
--category-color-general: #3b82f6;
--category-color-verification: #f97316;
--category-color-automation: #ec4899;
--category-color-developer: #6b7280;
--category-color-default: #6b7280;
/* Special section colors - same as light mode for consistency */
--special-color-favorites: #ffc107; /* Yellow/gold for favorites */
--special-color-recommended: #1bb1d4; /* Cyan for recommended */
--special-color-favorites: #ffc107;
--special-color-recommended: #1bb1d4;
/* Success (green) - dark */
--color-green-50: #052e16;
@@ -501,53 +431,39 @@
--color-yellow-900: #fef9c3;
/* Dark theme semantic colors */
--bg-surface: #2a2f36;
--bg-raised: #1f2329;
--bg-muted: #1f2329;
--bg-background: #2a2f36;
--bg-toolbar: #1f2329;
--bg-file-manager: #1f2329;
--bg-file-list: #2a2f36;
--bg-surface: #0f1318;
--bg-raised: #0a0d12;
--bg-muted: #0a0d12;
--bg-background: #0a0d12;
--bg-toolbar: #0a0d12;
--bg-file-manager: #0a0d12;
--bg-file-list: #0f1318;
--btn-open-file: #0a8bff;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-muted: #9ca3af;
/* Always-dark text (for use on light backgrounds like alerts) - does not change in dark mode */
--text-primary: #f0f2f5;
--text-secondary: #c5ccd6;
--text-muted: #8b96a3;
--text-always-dark: #1f2937;
--text-always-dark-muted: #6b7280;
--border-subtle: #2a2f36;
--border-default: #3a4047;
--border-strong: #4b5563;
--hover-bg: #374151;
--active-bg: #4b5563;
--border-subtle: #0f1318;
--border-default: #161c24;
--border-strong: #242c3a;
--hover-bg: #13181f;
--active-bg: #1a2030;
--automation-entry-hover-bg: var(--color-gray-200);
/* Icon colors for dark mode */
--icon-user-bg: #2a2f36;
--icon-user-color: #6e7581;
--icon-notifications-bg: #2a2f36;
--icon-notifications-color: #6e7581;
--icon-tools-bg: #4b525a;
--icon-tools-color: #eaeaea;
--icon-read-bg: #4b525a;
--icon-read-color: #eaeaea;
--icon-sign-bg: #4b525a;
--icon-sign-color: #eaeaea;
--icon-automate-bg: #4b525a;
--icon-automate-color: #eaeaea;
--icon-files-bg: #4b525a;
--icon-files-color: #eaeaea;
--icon-activity-bg: #4b525a;
--icon-activity-color: #eaeaea;
--icon-config-bg: #4b525a;
--icon-config-color: #eaeaea;
--icon-user-bg: #0f1318;
--icon-user-color: #5a6475;
--icon-tools-bg: #161c24;
--icon-tools-color: #d8dce4;
--icon-files-color: #d8dce4;
/* Inactive icon colors for dark mode */
--icon-inactive-bg: #2a2f36;
--icon-inactive-color: #6e7581;
--icon-inactive-bg: #0f1318;
--icon-inactive-color: #5a6475;
/* Dark mode tooltip colors */
--tooltip-title-bg: #4b525a;
--tooltip-title-bg: #161c24;
--tooltip-title-color: #fff;
--tooltip-header-bg: var(--bg-raised);
--tooltip-header-color: var(--text-primary);
@@ -555,7 +471,6 @@
--accent-interactive: #ffffff;
--text-instruction: #ffffff;
--text-brand: var(--color-gray-800);
--text-brand-accent: #ef4444;
/* Compare badge text colors (dark mode): lighter for readability */
@@ -563,62 +478,41 @@
--spdf-compare-added-badge-fg: var(--color-green-500);
/* container */
--landing-paper-bg: #171a1f;
--landing-inner-paper-bg: var(--bg-raised);
--landing-inner-paper-border: #2d3237;
--landing-inner-paper-border: #161c24;
/* landing dark overrides */
--landing-stack-glow-bg: radial-gradient(
circle,
rgba(30, 136, 229, 0.22) 0%,
transparent 70%
);
--landing-doc-shadow-back-idle:
0 4px 20px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.25);
--landing-doc-shadow-back-hover:
0 14px 44px rgba(0, 0, 0, 0.55), 0 6px 16px rgba(0, 0, 0, 0.35);
0 4px 20px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.4);
--landing-doc-shadow-front-idle:
0 8px 30px rgba(0, 0, 0, 0.45), 0 4px 12px rgba(0, 0, 0, 0.3),
0 8px 30px rgba(0, 0, 0, 0.7), 0 4px 12px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.04);
--landing-doc-shadow-front-hover:
0 20px 52px rgba(0, 0, 0, 0.6), 0 10px 24px rgba(0, 0, 0, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.06);
--landing-button-color: #ffffff;
--landing-button-hover-bg: var(--bg-raised);
/* selected file header colors for dark */
--header-selected-bg: #1e88e5;
--header-selected-fg: #ffffff;
/* file card background (dark) */
--file-card-bg: #1f2329;
--accordion-item-bg: #373d45; /* accordion item background - more distinguishable */
/* shadows */
--drop-shadow-color: rgba(255, 255, 255, 0.08);
--drop-shadow-color-strong: rgba(255, 255, 255, 0.04);
--drop-shadow-filter: drop-shadow(0 0.2rem 0.4rem rgba(200, 200, 200, 0.08))
drop-shadow(0 0.6rem 0.6rem rgba(200, 200, 200, 0.06))
drop-shadow(0 1.2rem 1rem rgba(200, 200, 200, 0.04));
--file-card-bg: #0a0d12;
--accordion-item-bg: #13181f;
/* Adjust shadows for dark mode */
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.4);
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.5);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.6);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.6);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.6);
--tools-text-and-icon-color: var(--text-primary);
/* Tool picker sticky header variables (dark mode) */
--tool-header-bg: #2a2f36;
--tool-header-border: #3a4047;
--tool-header-text: #d0d6dc;
--tool-header-badge-bg: #4b525a;
--tool-header-badge-text: #ffffff;
--tool-header-border: #161c24;
--tool-header-text: #c5ccd6;
--tool-header-badge-bg: #161c24;
--tool-header-badge-text: #f0f2f5;
/* Subcategory title styling (dark mode) */
--tool-subcategory-text-color: #9ca3af; /* lighter text in dark mode as well */
--tool-subcategory-rule-color: #3a4047; /* doubly lighter (relative) line in dark */
--tool-subcategory-text-color: #8b96a3;
--tool-subcategory-rule-color: #161c24;
/* PDF text selection colors (dark mode) */
--pdf-selection-bg: rgba(96, 165, 250, 0.25);
@@ -626,74 +520,67 @@
/* Placeholder text colors (dark mode) */
--search-text-and-icon-color: #ffffff !important;
--input-bg: #4b525a;
--input-bg: #161c24;
/* Tool panel search bar background colors (dark mode) */
--tool-panel-search-bg: #1f2329;
--tool-panel-search-border-bottom: #4b525a;
--tool-panel-search-bg: #0a0d12;
--tool-panel-search-border-bottom: #242c3a;
--information-text-bg: #292e34;
--information-text-color: #ececec;
--information-text-bg: #0a0d12;
--information-text-color: #d8dce4;
/* Bulk selection panel specific colors (dark mode) */
--bulk-panel-bg: var(--bg-raised); /* dark background for parent container */
--bulk-card-bg: var(--bg-raised); /* dark background for cards */
--bulk-card-border: var(
--border-default
); /* default border for cards and buttons */
--bulk-card-hover-border: var(--border-strong); /* stronger border on hover */
--unsupported-bar-bg: #1f2329;
--unsupported-bar-border: #4b525a;
--bulk-panel-bg: var(--bg-raised);
--bulk-card-bg: var(--bg-raised);
--bulk-card-border: var(--border-default);
--bulk-card-hover-border: var(--border-strong);
--unsupported-bar-bg: #0a0d12;
--unsupported-bar-border: #242c3a;
/* Config Modal colors (dark mode) */
--modal-nav-bg: #1f2329;
--modal-nav-section-title: #9ca3af;
--modal-nav-item: #d0d6dc;
--modal-nav-bg: #0a0d12;
--modal-nav-section-title: #8b96a3;
--modal-nav-item: #c5ccd6;
--modal-nav-item-active: #0a8bff;
--modal-nav-item-active-bg: rgba(10, 139, 255, 0.15);
--modal-content-bg: #2a2f36;
--modal-content-bg: #0f1318;
--modal-header-border: rgba(255, 255, 255, 0.08);
/* Onboarding (dark mode) */
--onboarding-title: #f9fafb;
--onboarding-body: #d1d5db;
--onboarding-title: #f0f2f5;
--onboarding-body: #c5ccd6;
--onboarding-primary-button-bg: #ffffff;
--onboarding-primary-button-text: #0b1220;
--onboarding-primary-button-text: #0a0d12;
--onboarding-secondary-button-bg: transparent;
--onboarding-secondary-button-text: #e5e7eb;
--onboarding-secondary-button-border: #3a4047;
--onboarding-step-active: #e5e7eb;
--onboarding-step-inactive: #4b5563;
--onboarding-secondary-button-text: #d8dce4;
--onboarding-secondary-button-border: #161c24;
--onboarding-step-active: #d8dce4;
--onboarding-step-inactive: #242c3a;
/* API Keys section colors (dark mode) */
--api-keys-card-bg: #2a2f36;
--api-keys-card-border: #3a4047;
--api-keys-card-bg: #0f1318;
--api-keys-card-border: #161c24;
--api-keys-card-shadow: none;
--api-keys-input-bg: #1f2329;
--api-keys-input-border: #3a4047;
--api-keys-button-bg: #3a4047;
--api-keys-button-color: #d0d6dc;
--api-keys-input-bg: #0a0d12;
--api-keys-input-border: #161c24;
--api-keys-button-bg: #161c24;
--api-keys-button-color: #c5ccd6;
/* Code token colors (dark mode - Cursor-like) */
--code-kw-color: #c792ea; /* purple */
--code-str-color: #c3e88d; /* green */
--code-num-color: #f78c6c; /* orange */
--code-com-color: #697098; /* muted gray-blue */
/* Compare tool specific colors (dark mode) - only for colors that don't have existing theme pairs */
--compare-upload-dropzone-bg: rgba(31, 35, 41, 0.45);
--compare-upload-dropzone-border: rgba(75, 85, 99, 0.6);
--compare-upload-icon-bg: rgba(75, 85, 99, 0.2);
--compare-upload-icon-color: rgba(243, 244, 246, 0.75);
--compare-upload-divider: rgba(75, 85, 99, 0.5);
--code-kw-color: #c792ea;
--code-str-color: #c3e88d;
--code-num-color: #f78c6c;
--code-com-color: #697098;
/* Compare page label chip (dark mode): slightly darker than surrounding rows */
--compare-page-label-bg: #1f2329;
/* Compare page label chip (dark mode) */
--compare-page-label-bg: #0a0d12;
--compare-page-label-fg: var(--text-secondary);
/* Status indicator dot ring shadow (dark) */
--status-dot-ring: 0 0 0 2px rgba(255, 255, 255, 0.18);
/* Input element styling (dark) */
--input-bg: #4b525a;
--input-bg: #161c24;
/* Select/dropdown placeholder state (dark) */
--select-placeholder-bg: var(--mantine-color-dark-5);
@@ -709,6 +596,195 @@
--dropdown-group-label: var(--mantine-color-dark-2);
}
/* ── Midnight theme — blue-tinted dark workbench ── */
/* Applied on top of dark mode: html has both data-mantine-color-scheme="dark"
and data-color-scheme="midnight" */
[data-color-scheme="midnight"] {
--gray-50: 21 26 38;
--gray-100: 30 36 52;
--gray-200: 45 50 72;
--gray-300: 58 65 90;
--gray-400: 75 85 99;
--gray-500: 107 114 128;
--gray-600: 156 163 175;
--gray-700: 209 213 219;
--gray-800: 229 231 235;
--gray-900: 243 244 246;
--surface: 30 36 52;
--background: 21 26 38;
--border: 58 65 90;
--color-gray-50: #151a24;
--color-gray-100: #1a2030;
--color-gray-200: #1e2435;
--color-gray-300: #252836;
--color-gray-400: #2d3340;
--color-gray-500: #6b7280;
--color-gray-600: #9ca3af;
--color-gray-700: #d1d5db;
--color-gray-800: #e5e7eb;
--color-gray-900: #f3f4f6;
--category-color-general: #4b77e6;
--bg-surface: #1e2435;
--bg-raised: #1a2030;
--bg-muted: #1a2030;
--bg-background: #151a24;
--bg-toolbar: #1e2435;
--bg-file-manager: #1a2030;
--bg-file-list: #1e2435;
--btn-open-file: #4b77e6;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-muted: #94a3b8;
--text-always-dark: #1f2937;
--text-always-dark-muted: #6b7280;
--border-subtle: #2d3340;
--border-default: #3a4152;
--border-strong: #4a5568;
--hover-bg: #252836;
--active-bg: #2d3340;
--automation-entry-hover-bg: var(--color-gray-200);
--icon-user-bg: #1e2435;
--icon-user-color: #6b7280;
--icon-tools-bg: #2d3340;
--icon-tools-color: #cbd5e1;
--icon-files-color: #4b77e6;
--icon-inactive-bg: #1e2435;
--icon-inactive-color: #6b7280;
--tooltip-title-bg: #252836;
--tooltip-title-color: #f1f5f9;
--tooltip-header-bg: #1a2030;
--tooltip-header-color: #f1f5f9;
--tooltip-border: var(--border-default);
--accent-interactive: #4b77e6;
--text-instruction: #4b77e6;
--text-brand-accent: #60a5fa;
--spdf-compare-removed-badge-fg: var(--color-red-500);
--spdf-compare-added-badge-fg: var(--color-green-500);
--landing-inner-paper-bg: #1a2030;
--landing-inner-paper-border: #3a4152;
--landing-button-bg: #252836;
--landing-button-color: #f1f5f9;
--landing-button-border: #3a4152;
--landing-drop-inner-paper-bg: #1e2435;
--landing-drop-inner-paper-border: #3a4152;
--landing-doc-shadow-back-idle:
0 4px 20px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.3);
--landing-doc-shadow-front-idle:
0 8px 30px rgba(0, 0, 0, 0.55), 0 4px 12px rgba(0, 0, 0, 0.4),
0 0 0 1px rgba(75, 119, 230, 0.08);
--header-selected-bg: #4b77e6;
--header-selected-fg: #ffffff;
--file-card-bg: #1a2030;
--accordion-item-bg: #252836;
--card-selected-border: #4b77e6;
--checkbox-checked-bg: #4b77e6;
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.5);
--tools-text-and-icon-color: #cbd5e1;
--tool-header-border: #3a4152;
--tool-header-text: #cbd5e1;
--tool-header-badge-bg: #252836;
--tool-header-badge-text: #f1f5f9;
--tool-subcategory-text-color: #94a3b8;
--tool-subcategory-rule-color: #3a4152;
--pdf-selection-bg: rgba(75, 119, 230, 0.25);
--pdf-selection-ring: rgba(75, 119, 230, 0.28);
--search-text-and-icon-color: #f1f5f9 !important;
--input-bg: #252836;
--tool-panel-search-bg: #1a2030;
--tool-panel-search-border-bottom: #3a4152;
--information-text-bg: #1e2435;
--information-text-color: #cbd5e1;
--bulk-panel-bg: var(--bg-raised);
--bulk-card-bg: var(--bg-raised);
--bulk-card-border: var(--border-default);
--bulk-card-hover-border: var(--border-strong);
--unsupported-bar-bg: #1a2030;
--unsupported-bar-border: #3a4152;
--modal-nav-bg: #1a2030;
--modal-nav-section-title: #94a3b8;
--modal-nav-item: #cbd5e1;
--modal-nav-item-active: #4b77e6;
--modal-nav-item-active-bg: rgba(75, 119, 230, 0.18);
--modal-content-bg: #1e2435;
--modal-header-border: rgba(255, 255, 255, 0.08);
--onboarding-title: #f1f5f9;
--onboarding-body: #cbd5e1;
--onboarding-primary-button-bg: #4b77e6;
--onboarding-primary-button-text: #ffffff;
--onboarding-secondary-button-bg: transparent;
--onboarding-secondary-button-text: #cbd5e1;
--onboarding-secondary-button-border: #3a4152;
--onboarding-step-active: #cbd5e1;
--onboarding-step-inactive: #3a4152;
--api-keys-card-bg: #1e2435;
--api-keys-card-border: #3a4152;
--api-keys-card-shadow: none;
--api-keys-input-bg: #1a2030;
--api-keys-input-border: #3a4152;
--api-keys-button-bg: #252836;
--api-keys-button-color: #cbd5e1;
--code-kw-color: #7aa2f7;
--code-str-color: #9ece6a;
--code-num-color: #ff9e64;
--code-com-color: #565f89;
--compare-page-label-bg: #1a2030;
--compare-page-label-fg: var(--text-secondary);
--status-dot-ring: 0 0 0 2px rgba(75, 119, 230, 0.25);
--select-placeholder-bg: var(--mantine-color-dark-5);
--select-placeholder-text: var(--mantine-color-dark-2);
--dropdown-trigger-bg: var(--mantine-color-dark-6);
--dropdown-trigger-text: var(--mantine-color-dark-0);
--dropdown-trigger-text-disabled: var(--mantine-color-dark-1);
--dropdown-trigger-icon: var(--mantine-color-dark-2);
--dropdown-panel-bg: var(--mantine-color-dark-7);
--dropdown-panel-border: var(--mantine-color-dark-4);
--dropdown-group-label: var(--mantine-color-dark-2);
--link-hover-bg: rgba(75, 119, 230, 0.06);
--link-hover-border: rgba(75, 119, 230, 0.2);
--link-hover-shadow:
0 0 0 0.5px rgba(75, 119, 230, 0.1), 0 1px 3px rgba(75, 119, 230, 0.08);
--link-focus-ring: rgba(75, 119, 230, 0.35);
--link-toolbar-bg: rgba(21, 26, 36, 0.94);
--link-toolbar-border: rgba(255, 255, 255, 0.1);
--link-toolbar-shadow:
0 12px 40px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3),
0 0 0 0.5px rgba(75, 119, 230, 0.08);
}
/* Dropzone drop state styling */
[data-accept] .dropzone-inner {
background-color: var(--landing-drop-inner-paper-bg) !important;
@@ -717,7 +793,6 @@
/* Plan section card borders - only override in dark mode */
[data-mantine-color-scheme="dark"] .plan-card {
--paper-border-color: rgb(158, 158, 158) !important;
}
[data-mantine-color-scheme="dark"] .plan-card [data-size="sm"] {
@@ -739,6 +814,19 @@
background-color: #1c598e !important;
}
[data-color-scheme="midnight"]
.plan-button:not(:disabled):not([data-disabled]) {
background-color: #4b77e6 !important;
}
/* Override Mantine's internal body/surface colors for midnight */
[data-color-scheme="midnight"] {
--mantine-color-body: #151a24;
--mantine-color-dark-6: #1e2435;
--mantine-color-dark-7: #151a24;
--mantine-color-dark-9: #0f1318;
}
/* Lighter grey for disabled plan buttons */
.plan-button:disabled,
.plan-button[data-disabled] {
@@ -787,12 +875,14 @@
border-radius: 8px;
}
/* Smooth transitions for theme switching */
/* Smooth transitions for theme switching - backgrounds and borders only.
Color (text) is intentionally excluded: Mantine applies the color scheme
after the first paint, so a color transition would cause text to visibly
crawl from the wrong colour on every page load. */
* {
transition:
background-color 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
border-color 0.2s ease;
}
/* ── PDF Link Overlay (viewer) ── */
:root {
@@ -992,10 +1082,15 @@
--shadow-color: rgba(15, 23, 42, 0.55);
}
[data-theme="dark"] {
[data-theme="dark"],
[data-color-scheme="dark"] {
--shadow-color: rgba(0, 0, 0, 0.75);
}
[data-color-scheme="midnight"] {
--shadow-color: rgba(0, 0, 0, 0.85);
}
/* ---- Wordmark light/dark switching (use instead of JS colorScheme checks) ---- */
.wordmark-dark-only {
display: none;
@@ -1099,3 +1194,17 @@
animation-duration: 160ms;
animation-timing-function: cubic-bezier(0.2, 0, 0.2, 1);
}
/* Mantine animates a button's background-color and its text/icon colour on
* separate transitions, so when a filled button flips state (selected ↔
* unselected, enabled ↔ disabled) you briefly see e.g. a blue background
* with the old dark-grey label still showing through. Drop the colour
* tween — the swap is instant, but transforms/shadows/borders/opacity keep
* animating for hover and press feedback. */
.mantine-Button-root,
.mantine-ActionIcon-root,
.mantine-SegmentedControl-control,
.mantine-SegmentedControl-label,
.mantine-SegmentedControl-indicator {
transition-property: transform, box-shadow, border-color, opacity !important;
}
@@ -31,9 +31,9 @@ test.describe("Navigation", () => {
await page.locator('a[href="/merge"]').first().click();
await expect(page).toHaveURL(/\/merge/);
// In the redesigned UI, "Back to tools" button replaces the old "Tools" link
// In the redesigned UI, "Back to all tools" button replaces the old "Tools" link
await page
.getByRole("button", { name: /Back to tools/i })
.getByRole("button", { name: /Back to all tools/i })
.first()
.click();
await expect(page).toHaveURL("/");
@@ -36,10 +36,10 @@ test.describe("4. PDF Tool Pages - Common Patterns", () => {
await page.goto("/compress");
await page.waitForLoadState("domcontentloaded");
// Step 2: Click the "Back to tools" button in ToolPanel to go back to /.
// Step 2: Click the "Back to all tools" button in ToolPanel to go back to /.
// In the redesigned UI this replaces the old "Tools" sidebar link.
const homeLink = page
.getByRole("button", { name: /Back to tools/i })
.getByRole("button", { name: /Back to all tools/i })
.first();
await homeLink.click();
@@ -53,6 +53,19 @@ const gray: MantineColorsTuple = [
"var(--color-gray-900)",
];
const dark: MantineColorsTuple = [
"#c4c9d2",
"#9aa3b0",
"#9ca3af",
"#6b7280",
"#242c3a",
"#161c24",
"#0f1318",
"#0a0d12",
"#070a0e",
"#040507",
];
export const mantineTheme = createTheme({
// Primary color
primaryColor: "primary",
@@ -63,6 +76,7 @@ export const mantineTheme = createTheme({
green,
yellow,
gray,
dark,
},
// Spacing system - uses CSS variables
@@ -58,6 +58,7 @@ export interface AppConfig {
timestampDefaultTsaUrl?: string;
timestampCustomTsaUrls?: string[];
timestampTsaPresets?: { label: string; url: string }[];
aiEngineEnabled?: boolean;
}
export type AppConfigBootstrapMode = "blocking" | "non-blocking";
@@ -0,0 +1,26 @@
import { flushSync } from "react-dom";
type ViewTransitionDoc = Document & {
startViewTransition?: (cb: () => void) => { finished: Promise<void> };
};
/**
* Run a state update inside a View Transition so the browser cross-fades
* (and morphs any elements sharing a {@code view-transition-name}) between
* the before/after DOMs.
*
* Falls back to a plain synchronous update when the API is unavailable
* (Firefox <130, JSDOM, motion-reduced preference).
*/
export function withViewTransition(update: () => void): Promise<void> {
if (typeof document === "undefined") {
update();
return Promise.resolve();
}
const doc = document as ViewTransitionDoc;
if (doc.startViewTransition) {
return doc.startViewTransition(() => flushSync(update)).finished;
}
update();
return Promise.resolve();
}
@@ -8,6 +8,7 @@ import { CheckoutProvider } from "@app/contexts/CheckoutContext";
import { UpgradeBannerInitializer } from "@app/components/shared/UpgradeBannerInitializer";
import { ServerExperienceProvider } from "@app/contexts/ServerExperienceContext";
import { UpdateSeatsProvider } from "@app/contexts/UpdateSeatsContext";
import { ChatProvider } from "@app/components/chat/ChatContext";
export function AppProviders({
children,
@@ -25,7 +26,7 @@ export function AppProviders({
<ServerExperienceProvider>
<CheckoutProvider>
<UpgradeBannerInitializer />
{children}
<ChatProvider>{children}</ChatProvider>
</CheckoutProvider>
</ServerExperienceProvider>
</UpdateSeatsProvider>
@@ -0,0 +1,363 @@
/* ─── Chat takeover ─────────────────────────────────────────────────────── */
.agents-takeover {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
background: var(--bg-toolbar);
z-index: 5;
view-transition-name: agents-rail;
}
/* ─── Sidebar agents section ─────────────────────────────────────────────── */
.agents-section {
padding: 0.5rem 0.75rem 0.875rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
flex-shrink: 0;
view-transition-name: agents-rail;
}
/* Hero button — real Stirling agent */
.agent-button {
display: block;
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border-subtle);
border-radius: 0.625rem;
background: var(--mantine-color-body);
transition:
background 150ms ease-out,
border-color 150ms ease-out;
}
.agent-button:hover {
border-color: color-mix(
in srgb,
var(--mantine-color-gray-4) 60%,
var(--border-subtle)
);
background: var(--mantine-color-default-hover);
}
.agent-button__logo {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--mantine-color-blue-filled);
flex-shrink: 0;
}
/* Coming-soon agent rows */
.agents-sidebar-list {
display: flex;
flex-direction: column;
gap: 0.375rem;
margin-top: 0.375rem;
}
.agent-button--coming-soon {
display: block;
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border-subtle);
border-radius: 0.625rem;
background: var(--mantine-color-body);
cursor: not-allowed !important;
opacity: 0.65;
transition:
opacity 180ms ease-out,
transform 180ms ease-out,
border-color 120ms ease-out;
}
@starting-style {
.agent-button--coming-soon {
opacity: 0;
transform: translateY(5px);
}
}
.agent-button--coming-soon:hover {
opacity: 0.85;
border-color: color-mix(
in srgb,
var(--mantine-color-gray-4) 60%,
var(--border-subtle)
);
background: var(--mantine-color-default-hover);
}
.agent-button__icon-plain {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
color: var(--mantine-color-dimmed);
flex-shrink: 0;
}
.agents-view-all {
all: unset;
display: block;
width: 100%;
margin-top: 0.375rem;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
color: var(--mantine-color-dimmed);
text-align: center;
cursor: pointer;
opacity: 0.7;
border-radius: 0.375rem;
transition: opacity 120ms ease-out;
box-sizing: border-box;
}
.agents-view-all:hover {
opacity: 1;
}
/* Dark mode — sidebar */
[data-mantine-color-scheme="dark"] .agent-button--coming-soon {
background: transparent;
border-color: color-mix(
in srgb,
var(--mantine-color-dark-4) 60%,
transparent
);
}
[data-mantine-color-scheme="dark"] .agent-button--coming-soon:hover {
border-color: var(--mantine-color-dark-3);
background: var(--mantine-color-dark-6);
}
[data-mantine-color-scheme="dark"] .agent-button {
background: transparent;
border-color: color-mix(
in srgb,
var(--mantine-color-dark-4) 60%,
transparent
);
}
[data-mantine-color-scheme="dark"] .agent-button:hover {
border-color: var(--mantine-color-dark-3);
background: var(--mantine-color-dark-6);
}
[data-mantine-color-scheme="dark"] .agent-button__logo {
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
}
/* ─── Collapsed-rail agent button ────────────────────────────────────────── */
.agents-collapsed-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
padding: 0;
border: none;
border-radius: 0.5rem;
background: transparent;
color: var(--mantine-color-blue-filled);
cursor: pointer;
transition: background 120ms ease-out;
}
.agents-collapsed-btn:hover {
background: var(--mantine-color-default-hover);
}
/* ─── Fullscreen hero card ───────────────────────────────────────────────── */
/*
* .tool-panel__fullscreen-group--agents gives the gradient border via the
* padding-box / border-box trick. We only override the padding-box with a
* very light tint so the card doesn't read as a colourful surface.
*/
.agents-hero {
padding: 1.25rem 1.5rem 1.5rem;
border-radius: 1rem;
background:
linear-gradient(
125deg,
color-mix(in srgb, var(--mantine-color-blue-1) 35%, white) 0%,
color-mix(in srgb, var(--mantine-color-violet-1) 45%, white) 50%,
color-mix(in srgb, var(--mantine-color-pink-0) 35%, white) 100%
)
padding-box,
linear-gradient(
135deg,
var(--mantine-color-blue-6) 0%,
var(--mantine-color-violet-6) 45%,
var(--mantine-color-pink-6) 100%
)
border-box;
}
/* Body: left CTA + right grid */
.agents-hero__body {
display: flex;
gap: 2rem;
align-items: flex-start;
}
/* ── Left CTA — plain content, only the button inside is interactive ── */
.agents-hero__cta {
display: flex;
flex-direction: column;
gap: 0;
flex: 0 0 18rem;
box-sizing: border-box;
padding: 0.25rem 0;
}
.agents-hero__cta-logo {
color: var(--mantine-color-blue-filled);
margin-bottom: 0.625rem;
}
.agents-hero__cta-headline {
font-size: 1.375rem;
}
.agents-hero__cta-btn {
all: unset;
margin-top: 1.25rem;
display: inline-flex;
align-items: center;
padding: 0.4375rem 1rem;
border-radius: 0.5rem;
background: var(--mantine-color-blue-6);
color: white;
font-size: 0.8125rem;
font-weight: 600;
line-height: 1;
width: fit-content;
cursor: pointer;
transition: background 0.15s ease;
}
.agents-hero__cta-btn:hover {
background: var(--mantine-color-blue-7);
}
.agents-hero__cta-btn:focus-visible {
outline: 2px solid var(--mantine-color-blue-5);
outline-offset: 3px;
border-radius: 0.5rem;
}
/* ── Right 2×3 grid — sizes to content, pushed to the right ── */
.agents-hero__grid {
flex: 0 0 auto;
margin-left: auto;
display: grid;
/* auto columns: each column = widest item in that column */
grid-template-columns: repeat(2, auto);
gap: 0.625rem;
align-content: start;
align-self: start;
}
/* Solid white cards — no opacity so the white is opaque */
.agents-hero__grid-item {
all: unset;
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 0.875rem;
border: 1px solid rgba(0, 0, 0, 0.07);
border-radius: 0.75rem;
background: var(--mantine-color-body);
cursor: not-allowed;
box-sizing: border-box;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.agents-hero__grid-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--mantine-color-blue-5);
}
.agents-hero__grid-body {
min-width: 0;
flex: 1;
}
/* ── Dark mode — fullscreen ── */
[data-mantine-color-scheme="dark"] .agents-hero {
background:
linear-gradient(
125deg,
color-mix(in srgb, var(--mantine-color-blue-9) 55%, #1a1b2e) 0%,
color-mix(in srgb, var(--mantine-color-violet-9) 45%, #1a1b2e) 50%,
color-mix(in srgb, var(--mantine-color-pink-9) 40%, #1a1b2e) 100%
)
padding-box,
linear-gradient(
135deg,
var(--mantine-color-blue-6) 0%,
var(--mantine-color-violet-6) 45%,
var(--mantine-color-pink-6) 100%
)
border-box;
}
[data-mantine-color-scheme="dark"] .agents-hero__cta-logo {
color: var(--mantine-color-blue-3);
}
[data-mantine-color-scheme="dark"] .agents-hero__grid-item {
background: var(--fullscreen-bg-item);
border-color: var(--fullscreen-border-subtle-70);
box-shadow: none;
}
[data-mantine-color-scheme="dark"] .agents-hero__grid-icon {
color: var(--mantine-color-blue-3);
}
/* ─── agents-rail view transition — slide up ─────────────────────────────── */
::view-transition-old(agents-rail) {
animation: vt-agents-out 150ms ease-out forwards;
}
::view-transition-new(agents-rail) {
animation: vt-agents-in 260ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
@keyframes vt-agents-out {
to {
opacity: 0;
}
}
@keyframes vt-agents-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(agents-rail),
::view-transition-new(agents-rail) {
animation-duration: 1ms !important;
}
}
@@ -0,0 +1,290 @@
import { useState } from "react";
import type { ElementType } from "react";
import { Box, Group, Text, UnstyledButton } from "@mantine/core";
import TableChartRoundedIcon from "@mui/icons-material/TableChartRounded";
import SummarizeRoundedIcon from "@mui/icons-material/SummarizeRounded";
import VisibilityOffRoundedIcon from "@mui/icons-material/VisibilityOffRounded";
import GavelRoundedIcon from "@mui/icons-material/GavelRounded";
import AssignmentRoundedIcon from "@mui/icons-material/AssignmentRounded";
import CodeRoundedIcon from "@mui/icons-material/CodeRounded";
import { useTranslation } from "react-i18next";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import { useChat } from "@app/components/chat/ChatContext";
import { ChatPanel } from "@app/components/chat/ChatPanel";
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
import { StirlingLogoOutline } from "@app/components/agents/StirlingLogoOutline";
import { withViewTransition } from "@app/utils/viewTransition";
import "@app/components/agents/AgentsPanel.css";
interface ComingSoonAgent {
id: string;
name: string;
description: string;
Icon: ElementType;
}
const COMING_SOON_AGENTS: ComingSoonAgent[] = [
{
id: "data-extraction",
name: "Data Extraction",
description: "Extract tables & structured data",
Icon: TableChartRoundedIcon,
},
{
id: "doc-summary",
name: "Summariser",
description: "Summarise long documents",
Icon: SummarizeRoundedIcon,
},
{
id: "auto-redaction",
name: "Auto Redaction",
description: "Redact PII automatically",
Icon: VisibilityOffRoundedIcon,
},
{
id: "compliance",
name: "Compliance Check",
description: "Audit documents for compliance",
Icon: GavelRoundedIcon,
},
{
id: "form-filler",
name: "Form Filler",
description: "Fill PDF forms intelligently",
Icon: AssignmentRoundedIcon,
},
{
id: "pdf-to-markdown",
name: "PDF to Markdown",
description: "Convert PDFs to clean Markdown",
Icon: CodeRoundedIcon,
},
];
export function useAgentsEnabled(): boolean {
const { config } = useAppConfig();
return Boolean(config?.aiEngineEnabled);
}
export function useAgentChatOpen(): boolean {
const { isOpen } = useChat();
return isOpen;
}
const PREVIEW_COUNT = 3;
/** Sidebar agents section — Stirling as hero CTA, coming-soon agents below. */
export function AgentsSection() {
const { t } = useTranslation();
const { isOpen, setOpen } = useChat();
const enabled = useAgentsEnabled();
const [showAll, setShowAll] = useState(false);
if (!enabled || isOpen) return null;
const comingSoonLabel = t("agents.coming_soon", "Coming soon");
const visibleAgents = showAll
? COMING_SOON_AGENTS
: COMING_SOON_AGENTS.slice(0, PREVIEW_COUNT);
return (
<Box className="agents-section" w="100%">
{/* Main Stirling agent — real and clickable */}
<UnstyledButton
className="agent-button agent-button--hero"
onClick={() => withViewTransition(() => setOpen(true))}
aria-label={t("agents.stirling_name", "Stirling")}
>
<Group gap="sm" wrap="nowrap" align="center">
<Box className="agent-button__logo">
<StirlingLogoOutline size={28} />
</Box>
<Box style={{ minWidth: 0, flex: 1 }}>
<Text size="sm" fw={600} truncate>
{t("agents.stirling_name", "Stirling")}
</Text>
<Text size="xs" c="dimmed" truncate>
{t(
"agents.stirling_description",
"Your general-purpose PDF assistant",
)}
</Text>
</Box>
</Group>
</UnstyledButton>
{/* Coming-soon agents */}
<div className="agents-sidebar-list">
{visibleAgents.map(({ id, name, description, Icon }) => (
<AppTooltip
key={id}
content={comingSoonLabel}
position="left"
arrow
delay={0}
>
<UnstyledButton
className="agent-button agent-button--coming-soon"
aria-disabled="true"
tabIndex={-1}
>
<Group gap="sm" wrap="nowrap" align="center">
<Box className="agent-button__icon-plain">
<Icon sx={{ fontSize: "1.1rem" }} />
</Box>
<Box style={{ minWidth: 0, flex: 1 }}>
<Text size="sm" fw={500} truncate>
{name}
</Text>
<Text size="xs" c="dimmed" truncate>
{description}
</Text>
</Box>
</Group>
</UnstyledButton>
</AppTooltip>
))}
</div>
{!showAll ? (
<button
type="button"
className="agents-view-all"
onClick={() => withViewTransition(() => setShowAll(true))}
>
{t("agents.view_all", "View all agents")}
</button>
) : (
<button
type="button"
className="agents-view-all"
onClick={() => withViewTransition(() => setShowAll(false))}
>
{t("agents.show_less", "Show less")}
</button>
)}
</Box>
);
}
/** Icon-only agent button in the collapsed (minimised) right rail. */
export function AgentsCollapsedButton({ onExpand }: { onExpand: () => void }) {
const { t } = useTranslation();
const { setOpen } = useChat();
const enabled = useAgentsEnabled();
if (!enabled) return null;
const label = t("agents.stirling_tooltip", "Stirling agent");
return (
<AppTooltip content={label} position="left" arrow delay={300}>
<UnstyledButton
onClick={() => {
onExpand();
setOpen(true);
}}
aria-label={label}
className="agents-collapsed-btn"
>
<StirlingLogoOutline size={22} />
</UnstyledButton>
</AppTooltip>
);
}
/**
* Fullscreen hero card — Stirling CTA on the left, 2×3 coming-soon grid on
* the right. Matches the gradient border of the other fullscreen category cards.
*/
export function AgentsFullscreenSection() {
const { t } = useTranslation();
const { isOpen, setOpen } = useChat();
const enabled = useAgentsEnabled();
if (!enabled || isOpen) return null;
const comingSoonLabel = t("agents.coming_soon", "Coming soon");
return (
<section
className="agents-hero tool-panel__fullscreen-group--agents"
aria-label={t("agents.section_title", "Agents")}
>
<div className="agents-hero__body">
{/* Left: Stirling content — only the button is interactive */}
<div className="agents-hero__cta">
<div className="agents-hero__cta-logo">
<StirlingLogoOutline size={36} />
</div>
<Text className="agents-hero__cta-headline" fw={700} lh={1.2} mt="xs">
{t("agents.stirling_full_name", "Stirling General Agent")}
</Text>
<Text size="sm" c="dimmed" mt={8} lh={1.55}>
{t(
"agents.stirling_long_description",
"General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents.",
)}
</Text>
<button
type="button"
className="agents-hero__cta-btn"
onClick={() => withViewTransition(() => setOpen(true))}
>
{t("agents.start_chat", "Start chatting")}
</button>
</div>
{/* Right: 2×3 grid of coming-soon agents */}
<div className="agents-hero__grid">
{COMING_SOON_AGENTS.map(({ id, name, description, Icon }) => (
<AppTooltip
key={id}
content={comingSoonLabel}
position="top"
arrow
delay={0}
>
<button
type="button"
className="agents-hero__grid-item"
aria-disabled="true"
>
<span className="agents-hero__grid-icon">
<Icon sx={{ fontSize: "1rem" }} />
</span>
<div className="agents-hero__grid-body">
<Text size="sm" fw={500} truncate>
{name}
</Text>
<Text size="xs" c="dimmed" truncate>
{description}
</Text>
</div>
</button>
</AppTooltip>
))}
</div>
</div>
</section>
);
}
/** Full-rail chat overlay rendered inside ToolPanel. */
export function AgentsChatOverlay() {
const { t } = useTranslation();
const { isOpen, setOpen } = useChat();
const enabled = useAgentsEnabled();
if (!enabled || !isOpen) return null;
return (
<Box className="agents-takeover">
<ChatPanel
onBack={() => withViewTransition(() => setOpen(false))}
backLabel={t("agents.back_to_tools", "Back to tools")}
/>
</Box>
);
}
@@ -0,0 +1,21 @@
/**
* A temp stirling logo, may change in future.
*/
export function StirlingLogoOutline({ size = 20 }: { size?: number }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 192 192"
fill="currentColor"
aria-hidden="true"
>
<path
d="M68.48 102.4 L184.73 6.45 L184.73 96.05 L68.48 192 Z"
opacity="0.5"
/>
<path d="M7.26 95.83 L123.37 0 L123.37 89.5 L7.26 185.33 Z" />
</svg>
);
}
@@ -0,0 +1,572 @@
import {
createContext,
useContext,
useReducer,
useCallback,
useRef,
type ReactNode,
} from "react";
import { useAllFiles, useFileActions } from "@app/contexts/FileContext";
import apiClient from "@app/services/apiClient";
import { getAuthHeaders } from "@app/services/apiClientSetup";
import { createChildStub } from "@app/contexts/file/fileActions";
import {
createNewStirlingFileStub,
createStirlingFile,
type StirlingFileStub,
} from "@app/types/fileContext";
import type { ToolOperation } from "@app/types/file";
export interface ChatMessage {
id: string;
role: "user" | "assistant";
content: string;
timestamp: number;
/**
* Tool endpoint paths executed during this assistant turn (e.g.
* {@code /api/v1/general/rotate-pdf}). Populated for assistant messages when the workflow
* ran one or more tools, in execution order. Undefined for user messages and for assistant
* turns that answered without running any tool.
*/
toolsUsed?: string[];
}
export enum AiWorkflowPhase {
ANALYZING = "analyzing",
CALLING_ENGINE = "calling_engine",
EXTRACTING_CONTENT = "extracting_content",
EXECUTING_TOOL = "executing_tool",
PROCESSING = "processing",
ENGINE_PROGRESS = "engine_progress",
}
/**
* Engine-side progress detail for ENGINE_PROGRESS events. Mirrors the Python
* {@code ProgressEvent} discriminated union (engine/src/stirling/contracts/progress.py)
* and the Java {@code AiEngineProgressDetail} sealed interface; the {@code phase}
* string is the discriminator. Field names are camelCase because the engine
* serialises by alias.
*/
export interface WholeDocReadStartedDetail {
phase: "whole_doc_read_started";
question: string;
pages: number;
slices: number;
}
export interface WholeDocSliceDoneDetail {
phase: "whole_doc_slice_done";
completed: number;
total: number;
/** Page-range label, e.g. "pages=1-5". */
pages: string;
durationMs: number;
excerpts: number;
facts: number;
}
export interface WholeDocCompressionRoundDetail {
phase: "whole_doc_compression_round";
roundNumber: number;
notesIn: number;
groups: number;
}
export interface WholeDocReadDoneDetail {
phase: "whole_doc_read_done";
completed: number;
slices: number;
durationSeconds: number;
}
export type EngineProgressDetail =
| WholeDocReadStartedDetail
| WholeDocSliceDoneDetail
| WholeDocCompressionRoundDetail
| WholeDocReadDoneDetail;
/**
* What we actually carry across the wire boundary: a known typed variant, or a
* forward-compat shape with just the discriminator string. The "unknown" arm
* exists so a new engine-side phase rolling out before a frontend update keeps
* rendering the generic processing message instead of crashing the union.
*/
export interface UnknownEngineProgressDetail {
phase: string;
}
export type AnyEngineProgressDetail =
| EngineProgressDetail
| UnknownEngineProgressDetail;
const KNOWN_ENGINE_PHASES = new Set<string>([
"whole_doc_read_started",
"whole_doc_slice_done",
"whole_doc_compression_round",
"whole_doc_read_done",
]);
export function isKnownEngineProgressDetail(
detail: AnyEngineProgressDetail,
): detail is EngineProgressDetail {
return KNOWN_ENGINE_PHASES.has(detail.phase);
}
export interface AiWorkflowProgress {
phase: AiWorkflowPhase;
/** Tool endpoint path currently executing, for EXECUTING_TOOL events. */
tool?: string;
/** 1-based step index, for EXECUTING_TOOL events. */
stepIndex?: number;
/** Total number of plan steps, for EXECUTING_TOOL events. */
stepCount?: number;
/**
* Engine-side event payload, for ENGINE_PROGRESS events. Typed sub-phase
* record (e.g. {@link WholeDocSliceDoneDetail}) the UI can render in detail.
*/
engineDetail?: AnyEngineProgressDetail;
}
type AiWorkflowOutcome =
| "answer"
| "not_found"
| "need_content"
| "plan"
| "need_clarification"
| "cannot_do"
| "tool_call"
| "completed"
| "unsupported_capability"
| "cannot_continue";
interface AiWorkflowResultFile {
/** Stirling file ID — download with /api/v1/general/files/{fileId}. */
fileId: string;
fileName: string;
contentType: string;
}
interface AiWorkflowResponse {
outcome: AiWorkflowOutcome;
answer?: string;
summary?: string;
rationale?: string;
reason?: string;
question?: string;
capability?: string;
message?: string;
evidence?: Array<{ pageNumber: number; text: string }>;
steps?: Array<Record<string, unknown>>;
/** Every file produced by the workflow (empty if the outcome has no files). */
resultFiles?: AiWorkflowResultFile[];
// Legacy single-file fields — mirror the first entry of resultFiles.
fileId?: string;
fileName?: string;
contentType?: string;
}
interface ChatState {
messages: ChatMessage[];
isOpen: boolean;
isLoading: boolean;
progress: AiWorkflowProgress | null;
}
type ChatAction =
| { type: "ADD_MESSAGE"; message: ChatMessage }
| { type: "SET_LOADING"; loading: boolean }
| { type: "SET_PROGRESS"; progress: AiWorkflowProgress | null }
| { type: "TOGGLE_OPEN" }
| { type: "SET_OPEN"; open: boolean }
| { type: "CLEAR" };
function chatReducer(state: ChatState, action: ChatAction): ChatState {
switch (action.type) {
case "ADD_MESSAGE":
return { ...state, messages: [...state.messages, action.message] };
case "SET_LOADING":
return { ...state, isLoading: action.loading };
case "SET_PROGRESS":
return { ...state, progress: action.progress };
case "TOGGLE_OPEN":
return { ...state, isOpen: !state.isOpen };
case "SET_OPEN":
return { ...state, isOpen: action.open };
case "CLEAR":
return { ...state, messages: [], isLoading: false, progress: null };
}
}
function formatWorkflowResponse(data: AiWorkflowResponse): string {
switch (data.outcome) {
case "answer":
case "completed":
return data.answer ?? data.summary ?? "Done.";
case "need_clarification":
return data.question ?? "Could you clarify your request?";
case "cannot_do":
return data.reason ?? "I'm unable to do that.";
case "not_found":
return data.reason ?? "I couldn't find the requested information.";
case "unsupported_capability":
return (
data.message ??
`Unsupported capability: ${data.capability ?? "unknown"}`
);
case "cannot_continue":
return data.reason ?? "Something went wrong and I can't continue.";
case "plan":
return data.rationale
? `${data.rationale}\n\n${(data.steps ?? []).map((s, i) => `${i + 1}. ${JSON.stringify(s)}`).join("\n")}`
: JSON.stringify(data.steps, null, 2);
case "need_content":
case "tool_call":
return (
data.rationale ?? data.summary ?? `Processing (${data.outcome})...`
);
default:
return (
data.answer ?? data.summary ?? data.message ?? JSON.stringify(data)
);
}
}
/**
* Parses an SSE text stream and invokes callbacks for each named event.
*/
interface ProgressEvent {
phase: string;
timestamp: number;
tool?: string;
stepIndex?: number;
stepCount?: number;
engineDetail?: AnyEngineProgressDetail;
}
async function consumeSSEStream(
response: Response,
handlers: {
onProgress: (data: ProgressEvent) => void;
onResult: (data: AiWorkflowResponse) => void;
onError: (data: { message: string }) => void;
},
) {
if (!response.body) {
throw new Error("Response body is null");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let currentEvent = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE frames are separated by double newlines
let boundary = buffer.indexOf("\n\n");
while (boundary !== -1) {
const frame = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
let dataPayload = "";
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) {
currentEvent = line.slice(6).trim();
} else if (line.startsWith("data:")) {
dataPayload += line.slice(5);
}
}
if (dataPayload) {
try {
const parsed = JSON.parse(dataPayload);
if (currentEvent === "progress") {
handlers.onProgress(parsed);
} else if (currentEvent === "result") {
handlers.onResult(parsed);
} else if (currentEvent === "error") {
handlers.onError(parsed);
}
} catch {
// Skip malformed JSON frames
}
}
currentEvent = "";
boundary = buffer.indexOf("\n\n");
}
}
}
interface ChatContextValue {
messages: ChatMessage[];
isOpen: boolean;
isLoading: boolean;
progress: AiWorkflowProgress | null;
toggleOpen: () => void;
setOpen: (open: boolean) => void;
sendMessage: (content: string) => Promise<void>;
/** Abort any in-flight request and reset the chat to an empty conversation. */
clearChat: () => void;
}
const ChatContext = createContext<ChatContextValue | null>(null);
const initialState: ChatState = {
messages: [],
isOpen: false,
isLoading: false,
progress: null,
};
export function ChatProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(chatReducer, initialState);
const { files: activeFiles, fileStubs: activeFileStubs } = useAllFiles();
const { actions: fileActions } = useFileActions();
const abortRef = useRef<AbortController | null>(null);
const messagesRef = useRef<ChatMessage[]>(state.messages);
messagesRef.current = state.messages;
// Download a File from the Stirling files endpoint.
const downloadFile = useCallback(
async (descriptor: AiWorkflowResultFile): Promise<File> => {
const response = await apiClient.get<Blob>(
`/api/v1/general/files/${descriptor.fileId}`,
{ responseType: "blob" },
);
return new File([response.data], descriptor.fileName, {
type: descriptor.contentType ?? response.data.type,
});
},
[],
);
// Import the files produced by an AI workflow result into FileContext.
//
// If the workflow produced the same number of outputs as inputs, map each output to its
// corresponding input as a new version in the same chain. Otherwise (merge, split, etc.)
// add the outputs as new root files.
const importResultFile = useCallback(
async (
result: AiWorkflowResponse,
sourceStubs: StirlingFileStub[],
): Promise<void> => {
const descriptors = result.resultFiles?.length
? result.resultFiles
: result.fileId && result.fileName && result.contentType
? [
{
fileId: result.fileId,
fileName: result.fileName,
contentType: result.contentType,
} satisfies AiWorkflowResultFile,
]
: [];
if (descriptors.length === 0) return;
const files = await Promise.all(descriptors.map(downloadFile));
const operation: ToolOperation = {
toolId: "ai-workflow",
timestamp: Date.now(),
};
const isVersionMapping =
sourceStubs.length > 0 && files.length === sourceStubs.length;
const stubs = files.map((file, i) =>
isVersionMapping
? createChildStub(sourceStubs[i], operation, file)
: createNewStirlingFileStub(file),
);
const stirlingFiles = files.map((file, i) =>
createStirlingFile(file, stubs[i].id),
);
if (sourceStubs.length > 0) {
// Always consume the inputs so merge/split inputs are removed from the workbench.
// For 1:1 operations (rotate, compress) the outputs carry the version chain; for
// merge/split they're fresh roots.
await fileActions.consumeFiles(
sourceStubs.map((s) => s.id),
stirlingFiles,
stubs,
);
} else {
// No inputs were provided (unlikely for completed workflows, but handle it safely).
await fileActions.addFiles(files, { selectFiles: true });
}
},
[fileActions, downloadFile],
);
const toggleOpen = useCallback(() => dispatch({ type: "TOGGLE_OPEN" }), []);
const setOpen = useCallback(
(open: boolean) => dispatch({ type: "SET_OPEN", open }),
[],
);
const clearChat = useCallback(() => {
abortRef.current?.abort();
abortRef.current = null;
dispatch({ type: "CLEAR" });
}, []);
const sendMessage = useCallback(
async (content: string) => {
// Abort any in-flight request
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
const priorMessages = messagesRef.current;
const userMessage: ChatMessage = {
id: crypto.randomUUID(),
role: "user",
content,
timestamp: Date.now(),
};
dispatch({ type: "ADD_MESSAGE", message: userMessage });
dispatch({ type: "SET_LOADING", loading: true });
dispatch({ type: "SET_PROGRESS", progress: null });
try {
const formData = new FormData();
formData.append("userMessage", content);
activeFiles.forEach((file, i) => {
formData.append(`fileInputs[${i}].fileInput`, file);
});
priorMessages.forEach((message, i) => {
formData.append(`conversationHistory[${i}].role`, message.role);
formData.append(`conversationHistory[${i}].content`, message.content);
});
const response = await fetch("/api/v1/ai/orchestrate/stream", {
method: "POST",
body: formData,
headers: getAuthHeaders(),
credentials: "include",
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`AI engine request failed: ${response.status}`);
}
let receivedResult = false;
const toolsUsed: string[] = [];
await consumeSSEStream(response, {
onProgress: (data) => {
if (
data.phase === AiWorkflowPhase.EXECUTING_TOOL &&
typeof data.tool === "string"
) {
toolsUsed.push(data.tool);
}
dispatch({
type: "SET_PROGRESS",
progress: {
phase: data.phase as AiWorkflowPhase,
tool: data.tool,
stepIndex: data.stepIndex,
stepCount: data.stepCount,
engineDetail: data.engineDetail,
},
});
},
onResult: (data) => {
receivedResult = true;
dispatch({ type: "SET_PROGRESS", progress: null });
const replyContent = formatWorkflowResponse(data);
dispatch({
type: "ADD_MESSAGE",
message: {
id: crypto.randomUUID(),
role: "assistant",
content: replyContent,
timestamp: Date.now(),
toolsUsed: toolsUsed.length > 0 ? toolsUsed : undefined,
},
});
if (data.fileId || data.resultFiles?.length) {
importResultFile(data, activeFileStubs).catch((err) => {
console.error("Failed to import AI result file", err);
dispatch({
type: "ADD_MESSAGE",
message: {
id: crypto.randomUUID(),
role: "assistant",
content:
"The file was processed but I couldn't download it.",
timestamp: Date.now(),
},
});
});
}
},
onError: (data) => {
receivedResult = true;
dispatch({ type: "SET_PROGRESS", progress: null });
dispatch({
type: "ADD_MESSAGE",
message: {
id: crypto.randomUUID(),
role: "assistant",
content: data.message || "Something went wrong.",
timestamp: Date.now(),
},
});
},
});
if (!receivedResult) {
throw new Error("Stream ended without a result");
}
} catch (e) {
if ((e as Error).name === "AbortError") return;
dispatch({ type: "SET_PROGRESS", progress: null });
dispatch({
type: "ADD_MESSAGE",
message: {
id: crypto.randomUUID(),
role: "assistant",
content:
"Failed to get a response. The AI engine may not be available yet.",
timestamp: Date.now(),
},
});
} finally {
dispatch({ type: "SET_LOADING", loading: false });
if (abortRef.current === controller) {
abortRef.current = null;
}
}
},
[activeFiles, activeFileStubs, importResultFile],
);
return (
<ChatContext.Provider
value={{
messages: state.messages,
isOpen: state.isOpen,
isLoading: state.isLoading,
progress: state.progress,
toggleOpen,
setOpen,
sendMessage,
clearChat,
}}
>
{children}
</ChatContext.Provider>
);
}
export function useChat(): ChatContextValue {
const context = useContext(ChatContext);
if (!context) {
throw new Error("useChat must be used within a ChatProvider");
}
return context;
}
@@ -0,0 +1,309 @@
.chat-panel {
display: flex;
flex-direction: column;
/* Match the right-rail toolbar background so dark-mode doesn't show a
lighter card behind the chat, and so the agent-card → pill morph
doesn't flash a different colour mid-transition. */
background: var(--bg-toolbar);
}
.chat-panel--embedded {
height: 100%;
width: 100%;
}
/* Top header: agent pill + close. */
.chat-panel__header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 1rem 0.75rem;
flex-shrink: 0;
}
.chat-panel__agent-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
flex: 1;
min-width: 0;
padding: 0.4rem 0.75rem 0.4rem 0.4rem;
border: 1px solid var(--border-subtle);
border-radius: 9999px;
background: var(--mantine-color-body);
cursor: pointer;
transition:
background 120ms ease-out,
border-color 120ms ease-out;
/* Pair with .agent-button to morph between the card and the pill. */
view-transition-name: stirling-agent;
}
/* Match the active-tool morph timing for a consistent rail feel. */
::view-transition-group(stirling-agent),
::view-transition-old(stirling-agent),
::view-transition-new(stirling-agent) {
animation-duration: 220ms;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
}
.chat-panel__agent-pill:hover {
background: var(--mantine-color-default-hover);
}
.chat-panel__agent-pill-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
border-radius: 9999px;
background: var(--mantine-color-blue-light);
color: var(--mantine-color-blue-filled);
flex-shrink: 0;
}
/* Dark-mode: let the pill blend into the rail. Just a thin border, no fill —
so it doesn't read as a clashing lighter card on the dark toolbar. */
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill {
background: transparent;
border-color: var(--border-subtle);
}
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill:hover {
background: rgba(255, 255, 255, 0.04);
}
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill-icon {
background: color-mix(
in srgb,
var(--mantine-color-blue-filled) 18%,
transparent
);
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
}
/* Same treatment for the input pill and quick-action cards. */
[data-mantine-color-scheme="dark"] .chat-panel-input {
background: transparent;
box-shadow:
0 0 0 1px var(--border-subtle),
0 6px 16px rgba(0, 0, 0, 0.25);
}
[data-mantine-color-scheme="dark"] .chat-panel-input:focus-within {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--mantine-color-blue-3) 40%, transparent),
0 8px 22px color-mix(in srgb, var(--mantine-color-blue-6) 18%, transparent);
}
[data-mantine-color-scheme="dark"] .chat-quick-action {
background: transparent;
}
[data-mantine-color-scheme="dark"] .chat-quick-action:hover {
background: rgba(255, 255, 255, 0.04);
border-color: var(--border-subtle);
}
.chat-panel__agent-pill-label {
font-size: 0.95rem;
font-weight: 600;
flex: 1;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-panel-messages {
flex: 1;
min-height: 0;
}
/* Quick action cards above the input. */
.chat-panel__quick-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.75rem 0.75rem 0.25rem;
flex-shrink: 0;
}
.chat-panel__quick-actions-label {
font-size: 0.95rem;
font-weight: 600;
margin-top: 0.25rem;
margin-bottom: 0.1rem;
}
/* File pills shown above the quick actions when files are loaded. */
.chat-file-pills {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
align-items: center;
}
.chat-file-pill {
display: inline-flex;
align-items: center;
gap: 0.3rem;
max-width: 11rem;
padding: 0.2rem 0.35rem 0.2rem 0.45rem;
border: 1px solid var(--mantine-color-blue-light);
border-radius: 9999px;
background: var(--mantine-color-blue-light);
color: var(--mantine-color-blue-filled);
font-size: 0.8rem;
font-weight: 500;
line-height: 1.1;
}
.chat-file-pill__icon {
flex-shrink: 0;
}
.chat-file-pill__label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-file-pill__remove {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
padding: 0;
border: none;
border-radius: 9999px;
background: transparent;
color: inherit;
cursor: pointer;
opacity: 0.7;
transition:
background 120ms ease-out,
opacity 120ms ease-out;
}
.chat-file-pill__remove:hover {
background: var(--mantine-color-default-hover);
opacity: 1;
}
.chat-file-pill--more {
background: transparent;
border: 1px dashed var(--border-subtle);
color: var(--text-muted);
cursor: pointer;
padding: 0.2rem 0.55rem;
}
.chat-file-pill--more:hover {
background: var(--mantine-color-default-hover);
}
.chat-quick-action {
display: block;
width: 100%;
padding: 0.6rem 0.75rem;
border: 1px solid var(--border-subtle);
border-radius: 0.65rem;
background: var(--mantine-color-body);
transition:
background 120ms ease-out,
border-color 120ms ease-out;
}
.chat-quick-action:hover {
background: var(--mantine-color-default-hover);
border-color: var(--mantine-color-default-border);
}
.chat-quick-action__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 0.5rem;
background: var(--mantine-color-blue-light);
color: var(--mantine-color-blue-filled);
flex-shrink: 0;
}
/* Input area: textarea on top row, action icons on bottom row. */
.chat-panel-input {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.85rem 1rem 0.75rem;
margin: 0.75rem 0.75rem 1rem;
border: none;
border-radius: 1.1rem;
background: var(--mantine-color-body);
flex-shrink: 0;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.04),
0 4px 14px rgba(15, 23, 42, 0.08);
transition: box-shadow 160ms ease-out;
}
.chat-panel-input:focus-within {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--mantine-color-blue-6) 20%, transparent),
0 6px 18px color-mix(in srgb, var(--mantine-color-blue-6) 12%, transparent);
}
/* Kill the Mantine Textarea's own border/outline — the wrapper owns the chrome. */
.chat-panel-input__field,
.chat-panel-input__field:focus,
.chat-panel-input__field:focus-visible {
font-size: 0.95rem;
padding: 0.15rem 0 !important;
background: transparent !important;
border: none !important;
outline: none !important;
box-shadow: none !important;
}
.chat-panel-input__actions {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Message layout */
.chat-message {
display: flex;
}
.chat-message-user {
justify-content: flex-end;
}
.chat-message-assistant {
justify-content: flex-start;
}
/* Bubble styling */
.chat-bubble {
max-width: 85%;
}
.chat-bubble-user {
background: var(--mantine-color-blue-filled) !important;
color: white !important;
}
.chat-bubble-user * {
color: white !important;
}
.chat-bubble-assistant {
background: var(--mantine-color-default-hover) !important;
}
@@ -0,0 +1,359 @@
import {
useMemo,
useRef,
useEffect,
useState,
type KeyboardEvent,
} from "react";
import { useTranslation } from "react-i18next";
import {
ActionIcon,
Box,
Collapse,
Group,
List,
Loader,
Menu,
Paper,
ScrollArea,
Stack,
Text,
Textarea,
UnstyledButton,
} from "@mantine/core";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
import CloseIcon from "@mui/icons-material/Close";
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import {
useChat,
AiWorkflowPhase,
isKnownEngineProgressDetail,
type AiWorkflowProgress,
type AnyEngineProgressDetail,
} from "@app/components/chat/ChatContext";
import { useTranslatedToolCatalog } from "@app/data/useTranslatedToolRegistry";
import { StirlingLogoOutline } from "@app/components/agents/StirlingLogoOutline";
import { ChatQuickActions } from "@app/components/chat/ChatQuickActions";
import "@app/components/chat/ChatPanel.css";
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
/** Resolver mapping a tool endpoint path to its translated display name. */
type ToolNameResolver = (endpoint: string) => string | null;
/**
* Look up a tool's translated name from the tool catalog. The catalog's {@code operationConfig}
* exposes the full API endpoint path for each tool, so we key the lookup on the exact path that
* arrives in SSE progress events — no string parsing.
*/
function useToolNameResolver(): ToolNameResolver {
const { allTools } = useTranslatedToolCatalog();
return useMemo(() => {
const nameByEndpoint = new Map<string, string>();
Object.values(allTools).forEach((tool) => {
const endpoint = tool.operationConfig?.endpoint;
if (typeof endpoint === "string") {
nameByEndpoint.set(endpoint, tool.name);
}
});
return (endpoint: string) => nameByEndpoint.get(endpoint) ?? null;
}, [allTools]);
}
function formatProgress(
progress: AiWorkflowProgress,
t: TranslateFn,
resolveToolName: ToolNameResolver,
): string {
if (progress.phase === AiWorkflowPhase.EXECUTING_TOOL && progress.tool) {
const tool = resolveToolName(progress.tool);
const hasSteps =
progress.stepIndex != null &&
progress.stepCount != null &&
progress.stepCount > 1;
if (tool) {
return hasSteps
? t("chat.progress.executing_tool_step", {
tool,
step: progress.stepIndex,
total: progress.stepCount,
})
: t("chat.progress.executing_tool_single", { tool });
}
return hasSteps
? t("chat.progress.executing_tool_generic_step", {
step: progress.stepIndex,
total: progress.stepCount,
})
: t("chat.progress.executing_tool_generic");
}
if (progress.phase === AiWorkflowPhase.ENGINE_PROGRESS) {
return formatEngineProgress(progress.engineDetail, t);
}
return t(`chat.progress.${progress.phase}`);
}
function formatEngineProgress(
detail: AnyEngineProgressDetail | undefined,
t: TranslateFn,
): string {
if (!detail || !isKnownEngineProgressDetail(detail)) {
return t("chat.progress.processing");
}
switch (detail.phase) {
case "whole_doc_read_started":
return t("chat.progress.whole_doc_read_started");
case "whole_doc_slice_done": {
const percent =
detail.total > 0
? Math.round((detail.completed / detail.total) * 100)
: 0;
return t("chat.progress.whole_doc_slice_done", { percent });
}
case "whole_doc_compression_round":
return t("chat.progress.whole_doc_compression_round");
case "whole_doc_read_done":
return t("chat.progress.whole_doc_read_done");
}
}
function ToolsUsedBlock({
tools,
resolveToolName,
t,
}: {
tools: string[];
resolveToolName: ToolNameResolver;
t: TranslateFn;
}) {
const [expanded, setExpanded] = useState(false);
const names = tools.map(
(endpoint) => resolveToolName(endpoint) ?? t("chat.toolsUsed.unknownTool"),
);
const label = t("chat.toolsUsed.summary", { count: tools.length });
return (
<Box mt={6}>
<UnstyledButton
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}
>
<Group gap={4} wrap="nowrap">
{expanded ? (
<ExpandLessIcon sx={{ fontSize: 14 }} />
) : (
<ExpandMoreIcon sx={{ fontSize: 14 }} />
)}
<Text size="xs" c="dimmed">
{label}
</Text>
</Group>
</UnstyledButton>
<Collapse in={expanded}>
<List
type="ordered"
size="xs"
mt={4}
pl="lg"
styles={{ itemWrapper: { lineHeight: 1.4 } }}
>
{names.map((name, i) => (
<List.Item key={i}>{name}</List.Item>
))}
</List>
</Collapse>
</Box>
);
}
function ChatMessageBubble({
role,
content,
toolsUsed,
resolveToolName,
t,
}: {
role: "user" | "assistant";
content: string;
toolsUsed?: string[];
resolveToolName: ToolNameResolver;
t: TranslateFn;
}) {
return (
<div className={`chat-message chat-message-${role}`}>
<Paper className={`chat-bubble chat-bubble-${role}`} p="xs" radius="md">
<Text size="sm" style={{ whiteSpace: "pre-wrap" }}>
{content}
</Text>
{toolsUsed && toolsUsed.length > 0 && (
<ToolsUsedBlock
tools={toolsUsed}
resolveToolName={resolveToolName}
t={t}
/>
)}
</Paper>
</div>
);
}
export interface ChatPanelProps {
/** Called when the user closes the chat to return to the tool list. */
onBack: () => void;
/** Accessible label for the close button. */
backLabel: string;
}
export function ChatPanel({ onBack, backLabel }: ChatPanelProps) {
const { t } = useTranslation();
const { messages, isLoading, progress, sendMessage, clearChat } = useChat();
const resolveToolName = useToolNameResolver();
const [input, setInput] = useState("");
const scrollRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTo({
top: scrollRef.current.scrollHeight,
behavior: "smooth",
});
}
}, [messages]);
useEffect(() => {
inputRef.current?.focus();
}, []);
const handleSend = (override?: string) => {
const text = (override ?? input).trim();
if (!text || isLoading) return;
setInput("");
sendMessage(text);
};
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
const showQuickActions = messages.length === 0 && !isLoading;
return (
<Box className="chat-panel chat-panel--embedded">
<div className="chat-panel__header">
<Menu shadow="md" width={220} position="bottom-start" withinPortal>
<Menu.Target>
<button
type="button"
className="chat-panel__agent-pill"
aria-label={t("chat.header.agentMenu", "Stirling agent options")}
>
<span className="chat-panel__agent-pill-icon">
<StirlingLogoOutline size={16} />
</span>
<span className="chat-panel__agent-pill-label">
{t("agents.stirling_name", "Stirling")}
</span>
<KeyboardArrowDownIcon
sx={{ fontSize: 18, color: "var(--text-muted)" }}
/>
</button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
leftSection={<DeleteSweepIcon sx={{ fontSize: 18 }} />}
onClick={clearChat}
disabled={messages.length === 0 && !isLoading}
>
{t("chat.header.clearChat", "Clear chat")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
<ActionIcon
variant="subtle"
color="gray"
size="md"
radius="xl"
onClick={onBack}
aria-label={backLabel}
>
<CloseIcon sx={{ fontSize: 18 }} />
</ActionIcon>
</div>
<ScrollArea className="chat-panel-messages" viewportRef={scrollRef}>
<Stack gap="sm" p="sm">
{messages.map((msg) => (
<ChatMessageBubble
key={msg.id}
role={msg.role}
content={msg.content}
toolsUsed={msg.toolsUsed}
resolveToolName={resolveToolName}
t={t}
/>
))}
{isLoading && (
<div className="chat-message chat-message-assistant">
<Paper
className="chat-bubble chat-bubble-assistant"
p="xs"
radius="md"
>
<Group gap="xs" wrap="nowrap">
<Loader size="xs" type="dots" />
<Text size="sm" c="dimmed">
{progress
? formatProgress(progress, t, resolveToolName)
: t("chat.progress.thinking")}
</Text>
</Group>
</Paper>
</div>
)}
</Stack>
</ScrollArea>
{showQuickActions && (
<ChatQuickActions
heading={t("chat.quickActions.heading", "Get started")}
onAction={(text) => handleSend(text)}
/>
)}
<div className="chat-panel-input">
<Textarea
ref={inputRef}
placeholder={t("chat.input.placeholder", "What do you want to do?")}
value={input}
onChange={(e) => setInput(e.currentTarget.value)}
onKeyDown={handleKeyDown}
disabled={isLoading}
autosize
minRows={1}
maxRows={4}
variant="unstyled"
classNames={{ input: "chat-panel-input__field" }}
/>
<div className="chat-panel-input__actions">
<ActionIcon
variant="filled"
color="blue"
radius="xl"
size="md"
onClick={() => handleSend()}
disabled={!input.trim() || isLoading}
aria-label={t("chat.input.send", "Send message")}
>
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
</ActionIcon>
</div>
</div>
</Box>
);
}
@@ -0,0 +1,303 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
import CloseIcon from "@mui/icons-material/Close";
import CompressIcon from "@mui/icons-material/Compress";
import ContentCutIcon from "@mui/icons-material/ContentCut";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFileOutlined";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import LayersIcon from "@mui/icons-material/Layers";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import RotateRightIcon from "@mui/icons-material/RotateRight";
import UploadFileIcon from "@mui/icons-material/UploadFile";
import { useAllFiles, useFileActions } from "@app/contexts/FileContext";
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
import { detectFileExtension, isPdfFile } from "@app/utils/fileUtils";
import type { StirlingFileStub } from "@app/types/fileContext";
const MAX_FILE_PILLS = 3;
interface QuickAction {
key: string;
icon: React.ReactNode;
title: string;
subtitle?: string;
onClick: () => void;
}
function QuickActionCard({ action }: { action: QuickAction }) {
return (
<UnstyledButton
className="chat-quick-action"
onClick={action.onClick}
aria-label={action.title}
>
<Group gap="sm" wrap="nowrap" align="center">
<Box className="chat-quick-action__icon">{action.icon}</Box>
<Box style={{ minWidth: 0, flex: 1 }}>
<Text size="sm" fw={500}>
{action.title}
</Text>
{action.subtitle && (
<Text size="xs" c="dimmed" truncate>
{action.subtitle}
</Text>
)}
</Box>
<KeyboardArrowDownIcon
sx={{
fontSize: 18,
transform: "rotate(-90deg)",
color: "var(--text-muted)",
}}
/>
</Group>
</UnstyledButton>
);
}
function WorkbenchFilePills({
stubs,
onOpenFilesModal,
onRemove,
moreLabel,
removeLabel,
}: {
stubs: StirlingFileStub[];
onOpenFilesModal: () => void;
onRemove: (id: StirlingFileStub["id"]) => void;
moreLabel: (count: number) => string;
removeLabel: (name: string) => string;
}) {
const visible = stubs.slice(0, MAX_FILE_PILLS);
const overflow = Math.max(0, stubs.length - visible.length);
return (
<div className="chat-file-pills">
{visible.map((stub) => (
<span key={stub.id} className="chat-file-pill">
<InsertDriveFileIcon
className="chat-file-pill__icon"
sx={{ fontSize: 14 }}
/>
<span className="chat-file-pill__label" title={stub.name}>
{stub.name}
</span>
<button
type="button"
className="chat-file-pill__remove"
onClick={() => onRemove(stub.id)}
aria-label={removeLabel(stub.name)}
>
<CloseIcon sx={{ fontSize: 12 }} />
</button>
</span>
))}
{overflow > 0 && (
<UnstyledButton
className="chat-file-pill chat-file-pill--more"
onClick={onOpenFilesModal}
>
{moreLabel(overflow)}
</UnstyledButton>
)}
</div>
);
}
interface WorkbenchSummary {
fileCount: number;
pdfCount: number;
nonPdfCount: number;
hasNonPdf: boolean;
singleFilePageCount: number | null;
typeBreakdown: { label: string; count: number }[];
}
function summariseWorkbench(stubs: StirlingFileStub[]): WorkbenchSummary {
const counts = new Map<string, number>();
let pdfCount = 0;
let nonPdfCount = 0;
for (const stub of stubs) {
const ext = detectFileExtension(stub.name ?? "");
const isPdf = isPdfFile({ name: stub.name, type: stub.type });
if (isPdf) pdfCount += 1;
else nonPdfCount += 1;
const label = ext ? ext.toUpperCase() : "FILE";
counts.set(label, (counts.get(label) ?? 0) + 1);
}
const typeBreakdown = Array.from(counts.entries())
.sort((a, b) => b[1] - a[1])
.map(([label, count]) => ({ label, count }));
return {
fileCount: stubs.length,
pdfCount,
nonPdfCount,
hasNonPdf: nonPdfCount > 0,
singleFilePageCount:
stubs.length === 1 ? (stubs[0].processedFile?.totalPages ?? null) : null,
typeBreakdown,
};
}
export interface ChatQuickActionsProps {
/** Heading text shown above the actions. */
heading: string;
/** Invoked when the user selects an action — sends the given text as a chat message. */
onAction: (text: string) => void;
}
export function ChatQuickActions({ heading, onAction }: ChatQuickActionsProps) {
const { t } = useTranslation();
const { fileStubs } = useAllFiles();
const { actions: fileActions } = useFileActions();
const { openFilesModal } = useFilesModalContext();
const summary = useMemo(() => summariseWorkbench(fileStubs), [fileStubs]);
const actions = useMemo<QuickAction[]>(() => {
const send = (text: string) => () => onAction(text);
if (summary.fileCount === 0) {
return [
{
key: "open-from-computer",
icon: <UploadFileIcon sx={{ fontSize: 18 }} />,
title: t("chat.quickActions.openFromComputer", "Open from computer"),
subtitle: t("chat.quickActions.browseYourFiles", "Browse your files"),
onClick: () => openFilesModal(),
},
];
}
if (summary.fileCount === 1) {
// Non-PDF: only suggest converting to PDF.
if (summary.hasNonPdf) {
const text = t(
"chat.quickActions.convertOne",
"Convert this document to PDF",
);
return [
{
key: "convert",
icon: <PictureAsPdfIcon sx={{ fontSize: 18 }} />,
title: text,
onClick: send(text),
},
];
}
const result: QuickAction[] = [];
const hasMultiplePages =
summary.singleFilePageCount != null && summary.singleFilePageCount > 1;
if (hasMultiplePages) {
const text = t("chat.quickActions.splitOne", "Split this document");
result.push({
key: "split",
icon: <ContentCutIcon sx={{ fontSize: 18 }} />,
title: text,
onClick: send(text),
});
}
const compressText = t(
"chat.quickActions.compressOne",
"Compress this document",
);
const rotateText = t(
"chat.quickActions.rotateOne",
"Rotate this document",
);
result.push({
key: "compress",
icon: <CompressIcon sx={{ fontSize: 18 }} />,
title: compressText,
onClick: send(compressText),
});
result.push({
key: "rotate",
icon: <RotateRightIcon sx={{ fontSize: 18 }} />,
title: rotateText,
onClick: send(rotateText),
});
return result;
}
// Multiple files.
const result: QuickAction[] = [];
if (summary.hasNonPdf) {
const text = t(
"chat.quickActions.convertMany",
"Convert these documents to PDF",
);
result.push({
key: "convert",
icon: <PictureAsPdfIcon sx={{ fontSize: 18 }} />,
title: text,
onClick: send(text),
});
}
const mergeText = t("chat.quickActions.mergeMany", {
count: summary.fileCount,
defaultValue: "Merge these {{count}} documents into 1",
});
const compressText = t(
"chat.quickActions.compressMany",
"Compress these documents",
);
const rotateText = t(
"chat.quickActions.rotateMany",
"Rotate these documents",
);
result.push({
key: "merge",
icon: <LayersIcon sx={{ fontSize: 18 }} />,
title: mergeText,
onClick: send(mergeText),
});
result.push({
key: "compress",
icon: <CompressIcon sx={{ fontSize: 18 }} />,
title: compressText,
onClick: send(compressText),
});
result.push({
key: "rotate",
icon: <RotateRightIcon sx={{ fontSize: 18 }} />,
title: rotateText,
onClick: send(rotateText),
});
return result;
}, [summary, t, onAction, openFilesModal]);
return (
<div className="chat-panel__quick-actions">
{summary.fileCount > 0 && (
<WorkbenchFilePills
stubs={fileStubs}
onOpenFilesModal={() => openFilesModal()}
onRemove={(id) => fileActions.removeFiles([id])}
moreLabel={(count) =>
t("chat.quickActions.moreFiles", {
count,
defaultValue: "+{{count}} more",
})
}
removeLabel={(name) =>
t("chat.quickActions.removeFile", {
name,
defaultValue: "Remove {{name}}",
})
}
/>
)}
<Text className="chat-panel__quick-actions-label">{heading}</Text>
<Stack gap="xs">
{actions.map((action) => (
<QuickActionCard key={action.key} action={action} />
))}
</Stack>
</div>
);
}
@@ -8,5 +8,8 @@ import { type ProprietaryToolRegistry } from "@app/data/toolsTaxonomy";
* and will be included in the main tool registry.
*/
export function useProprietaryToolRegistry(): ProprietaryToolRegistry {
return useMemo<ProprietaryToolRegistry>(() => ({}), []);
// "ai-workflow" is a marker super-tool (see proprietaryToolId.ts) with no
// registry entry, so we widen via `as`. Add real proprietary tools here as
// `{ id: { ... } }` entries.
return useMemo(() => ({}) as ProprietaryToolRegistry, []);
}
@@ -6,7 +6,11 @@
export const PROPRIETARY_REGULAR_TOOL_IDS = [] as const;
export const PROPRIETARY_SUPER_TOOL_IDS = [] as const;
// "ai-workflow" is a generic marker stamped onto files produced by the agents
// chat orchestrator (which may invoke one or more underlying tools). Lives here
// as a super-tool so ``ToolOperation.toolId`` stays typed; not user-pickable —
// see ChatContext.tsx. The tool registry doesn't include it as an entry.
export const PROPRIETARY_SUPER_TOOL_IDS = ["ai-workflow"] as const;
export const PROPRIETARY_LINK_TOOL_IDS = [] as const;
@@ -1,20 +0,0 @@
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
import { type AppProvidersProps } from "@core/components/AppProviders";
import { ChatProvider } from "@app/components/chat/ChatContext";
export type { AppProvidersProps };
export function AppProviders({
children,
appConfigRetryOptions,
appConfigProviderProps,
}: AppProvidersProps) {
return (
<ProprietaryAppProviders
appConfigRetryOptions={appConfigRetryOptions}
appConfigProviderProps={appConfigProviderProps}
>
<ChatProvider>{children}</ChatProvider>
</ProprietaryAppProviders>
);
}
@@ -1,77 +0,0 @@
.chat-toggle-button {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.chat-panel {
position: fixed;
top: 0;
right: 0;
width: 380px;
height: 100vh;
display: flex;
flex-direction: column;
background: var(--mantine-color-body);
border-left: 1px solid
var(--border-subtle, var(--mantine-color-default-border));
z-index: 999;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
}
.chat-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid
var(--border-subtle, var(--mantine-color-default-border));
flex-shrink: 0;
}
.chat-panel-messages {
flex: 1;
min-height: 0;
}
.chat-panel-input {
display: flex;
gap: 0.5rem;
padding: 0.75rem;
border-top: 1px solid
var(--border-subtle, var(--mantine-color-default-border));
flex-shrink: 0;
}
/* Message layout */
.chat-message {
display: flex;
}
.chat-message-user {
justify-content: flex-end;
}
.chat-message-assistant {
justify-content: flex-start;
}
/* Bubble styling */
.chat-bubble {
max-width: 85%;
}
.chat-bubble-user {
background: var(--mantine-color-blue-filled) !important;
color: white !important;
}
.chat-bubble-user * {
color: white !important;
}
.chat-bubble-assistant {
background: var(--mantine-color-default-hover) !important;
}
@@ -1,5 +0,0 @@
import { ChatPanel } from "@app/components/chat/ChatPanel";
export function HomePageExtensions() {
return <ChatPanel />;
}
+219
View File
@@ -0,0 +1,219 @@
// Auto-generated icon types
// This file is automatically generated by scripts/generate-icons.js
// Do not edit manually - changes will be overwritten
export type MaterialSymbolIcon =
| "123-rounded"
| "account-circle"
| "add"
| "add-comment"
| "add-photo-alternate"
| "add-rounded"
| "admin-panel-settings"
| "admin-panel-settings-rounded"
| "analytics"
| "analytics-rounded"
| "api-rounded"
| "approval-outline-rounded"
| "arrow-back"
| "arrow-back-rounded"
| "arrow-forward"
| "article-outline-rounded"
| "assignment-outline-rounded"
| "attachment-rounded"
| "auto-awesome-rounded"
| "automation-outline"
| "backup"
| "bookmark-add-rounded"
| "branding-watermark-outline-rounded"
| "brush"
| "build-outline-rounded"
| "change-history"
| "check"
| "check-circle"
| "check-circle-outline-rounded"
| "check-circle-rounded"
| "chevron-left"
| "chevron-left-rounded"
| "chevron-right-rounded"
| "cleaning-services-outline-rounded"
| "close"
| "close-rounded"
| "cloud-upload"
| "comment"
| "compare-rounded"
| "compress"
| "content-copy"
| "content-copy-rounded"
| "content-cut-rounded"
| "content-paste-rounded"
| "credit-card"
| "crop-free-rounded"
| "crop-rounded"
| "crop-square"
| "crop-square-outline"
| "dashboard-customize-outline-rounded"
| "dashboard-customize-rounded"
| "dashboard-outline-rounded"
| "delete"
| "delete-outline-rounded"
| "delete-rounded"
| "description"
| "desktop-cloud-rounded"
| "download"
| "download-rounded"
| "edit"
| "edit-outline-rounded"
| "edit-rounded"
| "edit-square-outline-rounded"
| "edit-square-rounded"
| "error"
| "error-outline-rounded"
| "error-rounded"
| "expand-more-rounded"
| "explore-rounded"
| "extension-rounded"
| "fact-check-outline-rounded"
| "fact-check-rounded"
| "find-replace"
| "folder-rounded"
| "format-align-center"
| "format-align-left"
| "format-align-right"
| "format-color-fill-rounded"
| "format-underlined"
| "gavel-rounded"
| "gpp-maybe-outline-rounded"
| "group"
| "group-add"
| "group-rounded"
| "groups-rounded"
| "help-rounded"
| "highlight"
| "image-outline-rounded"
| "info"
| "info-outline-rounded"
| "info-rounded"
| "javascript-rounded"
| "key"
| "key-rounded"
| "keyboard-arrow-down"
| "keyboard-arrow-up"
| "keyboard-rounded"
| "language"
| "layers-clear-outline-rounded"
| "layers-outline-rounded"
| "library-add-outline-rounded"
| "link"
| "link-rounded"
| "lock"
| "lock-open"
| "lock-open-right-outline-rounded"
| "lock-outline"
| "login"
| "logout"
| "logout-rounded"
| "looks-one-outline-rounded"
| "mail-rounded"
| "match-word-rounded"
| "material-symbols:pause-rounded"
| "material-symbols:play-arrow-rounded"
| "menu-book-outline-rounded"
| "menu-book-rounded"
| "more-vert"
| "move-down-rounded"
| "opacity"
| "open-in-new-rounded"
| "palette-outline"
| "pan-tool-rounded"
| "password-rounded"
| "pause-rounded"
| "person"
| "person-add"
| "person-add-rounded"
| "person-check"
| "person-off"
| "person-remove"
| "person-rounded"
| "photo-library-outline-rounded"
| "picture-as-pdf-rounded"
| "pin-end"
| "play-arrow-rounded"
| "play-circle"
| "preview-off-rounded"
| "qr-code-rounded"
| "quick-reference-all-outline-rounded"
| "radio-button-unchecked"
| "redo"
| "refresh"
| "refresh-rounded"
| "remove-moderator-outline-rounded"
| "remove-selection-rounded"
| "rocket-launch"
| "rotate-left"
| "rotate-right"
| "rotate-right-rounded"
| "save"
| "save-rounded"
| "scan-delete-outline-rounded"
| "scan-delete-rounded"
| "scanner-outline-rounded"
| "schedule-outline-rounded"
| "search"
| "search-rounded"
| "security"
| "select-all"
| "send-rounded"
| "settings-rounded"
| "shield-lock"
| "shield-rounded"
| "show-chart"
| "signature-rounded"
| "speed"
| "star"
| "star-rounded"
| "sticky-note-2"
| "storage-rounded"
| "strikethrough-s"
| "subdirectory-arrow-right-rounded"
| "swap-horiz"
| "sync-alt-rounded"
| "system-update-alt-rounded"
| "text-fields"
| "text-fields-rounded"
| "thread-unread-rounded"
| "toc-rounded"
| "touch-app-rounded"
| "tune-rounded"
| "undo"
| "unfold-less"
| "unfold-more"
| "upload-file-rounded"
| "upload-rounded"
| "verified-outline-rounded"
| "verified-user-outline"
| "verified-user-rounded"
| "view-carousel-rounded"
| "view-list"
| "visibility"
| "visibility-off-outline-rounded"
| "visibility-rounded"
| "vital-signs-rounded"
| "warning"
| "warning-rounded"
| "workspace-premium-outline-rounded"
| "workspace-premium-rounded"
| "zoom-in"
| "zoom-in-map-rounded"
| "zoom-out";
export interface IconSet {
prefix: string;
icons: Record<string, any>;
width?: number;
height?: number;
}
// Re-export the icon set as the default export with proper typing
declare const iconSet: IconSet;
export default iconSet;
@@ -0,0 +1,622 @@
{
"prefix": "material-symbols",
"icons": {
"123-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5.5 10.5h-.75q-.325 0-.537-.213T4 9.75t.213-.537T4.75 9h1.5q.325 0 .538.213T7 9.75v4.5q0 .325-.213.538T6.25 15t-.537-.213t-.213-.537zM9 14.25V12.5q0-.425.288-.712T10 11.5h2v-1H9.75q-.325 0-.537-.213T9 9.75t.213-.537T9.75 9h2.75q.425 0 .713.288T13.5 10v1.5q0 .425-.288.713t-.712.287h-2v1h2.25q.325 0 .538.213t.212.537t-.213.538t-.537.212h-3q-.325 0-.537-.213T9 14.25m9.5.75h-2.75q-.325 0-.537-.213T15 14.25t.213-.537t.537-.213H18v-1h-1.5q-.2 0-.35-.15T16 12t.15-.35t.35-.15H18v-1h-2.25q-.325 0-.537-.213T15 9.75t.213-.537T15.75 9h2.75q.425 0 .713.288T19.5 10v4q0 .425-.288.713T18.5 15\"/>"
},
"account-circle": {
"body": "<path fill=\"currentColor\" d=\"M5.85 17.1q1.275-.975 2.85-1.537T12 15t3.3.563t2.85 1.537q.875-1.025 1.363-2.325T20 12q0-3.325-2.337-5.663T12 4T6.337 6.338T4 12q0 1.475.488 2.775T5.85 17.1m3.663-5.113Q8.5 10.976 8.5 9.5t1.013-2.488T12 6t2.488 1.013T15.5 9.5t-1.012 2.488T12 13t-2.488-1.012M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"add": {
"body": "<path fill=\"currentColor\" d=\"M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z\"/>"
},
"add-comment": {
"body": "<path fill=\"currentColor\" d=\"M11 14h2v-3h3V9h-3V6h-2v3H8v2h3zm-9 8V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18H6z\"/>"
},
"add-photo-alternate": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h9q-.5.65-.75 1.425T13 6q0 2.075 1.463 3.538T18 11q.8 0 1.575-.25T21 10v9q0 .825-.587 1.413T19 21zm1-4h12l-3.75-5l-3 4L9 13zm11-8V7h-2V5h2V3h2v2h2v2h-2v2z\"/>"
},
"add-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11 13H6q-.425 0-.712-.288T5 12t.288-.712T6 11h5V6q0-.425.288-.712T12 5t.713.288T13 6v5h5q.425 0 .713.288T19 12t-.288.713T18 13h-5v5q0 .425-.288.713T12 19t-.712-.288T11 18z\"/>"
},
"admin-panel-settings": {
"body": "<path fill=\"currentColor\" d=\"M17 22q-2.075 0-3.537-1.463T12 17t1.463-3.537T17 12t3.538 1.463T22 17t-1.463 3.538T17 22m-5 0q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v5.675q-.65-.325-1.463-.5T17 10q-2.9 0-4.95 2.05T10 17q0 1.55.588 2.8t1.487 2.175q-.025 0-.037.013T12 22m6.063-5.437q.437-.438.437-1.063t-.437-1.062T17 14t-1.062.438T15.5 15.5t.438 1.063T17 17t1.063-.437M17 20q.775 0 1.425-.363t1.05-.962q-.55-.325-1.175-.5T17 18t-1.3.175t-1.175.5q.4.6 1.05.963T17 20\"/>"
},
"admin-panel-settings-rounded": {
"body": "<path fill=\"currentColor\" d=\"M17 22q-2.075 0-3.537-1.463T12 17t1.463-3.537T17 12t3.538 1.463T22 17t-1.463 3.538T17 22M4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375v3.05q0 .425-.375.688t-.8.137q-.45-.125-.9-.187T17 10q-2.9 0-4.95 2.05T10 17q0 .8.188 1.563t.537 1.537q.225.475-.137.85t-.838.15q-1.05-.55-1.925-1.35T6.275 18Q5.2 16.525 4.6 14.762T4 11.1m14.063 5.463q.437-.438.437-1.063t-.437-1.062T17 14t-1.062.438T15.5 15.5t.438 1.063T17 17t1.063-.437M17 20q.625 0 1.175-.238t.975-.687q.125-.15.1-.337t-.225-.288q-.475-.225-.987-.337T17 18t-1.037.113t-.988.337q-.2.1-.225.288t.1.337q.425.45.975.688T17 20\"/>"
},
"analytics": {
"body": "<path fill=\"currentColor\" d=\"M7 17h2v-5H7zm8 0h2V7h-2zm-4 0h2v-3h-2zm0-5h2v-2h-2zm-6 9q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
},
"analytics-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7.288 12.288Q7 12.575 7 13v3q0 .425.288.713T8 17t.713-.288T9 16v-3q0-.425-.288-.712T8 12t-.712.288m8-5Q15 7.575 15 8v8q0 .425.288.713T16 17t.713-.288T17 16V8q0-.425-.288-.712T16 7t-.712.288m-4 7Q11 14.575 11 15v1q0 .425.288.713T12 17t.713-.288T13 16v-1q0-.425-.288-.712T12 14t-.712.288M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm7.713-9.287Q13 11.425 13 11t-.288-.712T12 10t-.712.288T11 11t.288.713T12 12t.713-.288\"/>"
},
"api-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 14l-2-2l2-2l2 2zM9.875 8.125l-2.5-2.5l3.2-3.2q.3-.3.675-.45t.75-.15t.75.15t.675.45l3.2 3.2l-2.5 2.5L12 6zm-4.25 8.5l-3.2-3.2q-.3-.3-.45-.675t-.15-.75t.15-.75t.45-.675l3.2-3.2l2.5 2.5L6 12l2.125 2.125zm12.75 0l-2.5-2.5L18 12l-2.125-2.125l2.5-2.5l3.2 3.2q.3.3.45.675t.15.75t-.15.75t-.45.675zm-7.8 4.95l-3.2-3.2l2.5-2.5L12 18l2.125-2.125l2.5 2.5l-3.2 3.2q-.3.3-.675.45t-.75.15t-.75-.15t-.675-.45\"/>"
},
"approval-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20v-4q0-.825.588-1.412T6 14h12q.825 0 1.413.588T20 16v4q0 .825-.587 1.413T18 22zm1-4h10q.425 0 .713-.288T18 17t-.288-.712T17 16H7q-.425 0-.712.288T6 17t.288.713T7 18m4.175-5.15l-3.7-5.175q-.225-.325-.312-.712t-.038-.788q.3-1.825 1.65-3T12 2t3.225 1.175t1.65 3q.05.4-.038.788t-.312.712l-3.7 5.175q-.3.425-.825.425t-.825-.425M12 11.2L15 7q0-1.25-.875-2.125T12 4t-2.125.875T9 7zm0-3.6\"/>"
},
"arrow-back": {
"body": "<path fill=\"currentColor\" d=\"m7.825 13l5.6 5.6L12 20l-8-8l8-8l1.425 1.4l-5.6 5.6H20v2z\"/>"
},
"arrow-back-rounded": {
"body": "<path fill=\"currentColor\" d=\"m7.825 13l4.9 4.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-6.6-6.6q-.15-.15-.213-.325T4.426 12t.063-.375t.212-.325l6.6-6.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L7.825 11H19q.425 0 .713.288T20 12t-.288.713T19 13z\"/>"
},
"arrow-forward": {
"body": "<path fill=\"currentColor\" d=\"M16.175 13H4v-2h12.175l-5.6-5.6L12 4l8 8l-8 8l-1.425-1.4z\"/>"
},
"article-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14zm3 12h5q.425 0 .713-.288T14 16t-.288-.712T13 15H8q-.425 0-.712.288T7 16t.288.713T8 17m0-4h8q.425 0 .713-.288T17 12t-.288-.712T16 11H8q-.425 0-.712.288T7 12t.288.713T8 13m0-4h8q.425 0 .713-.288T17 8t-.288-.712T16 7H8q-.425 0-.712.288T7 8t.288.713T8 9\"/>"
},
"assignment-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4.2q.325-.9 1.088-1.45T12 1t1.713.55T14.8 3H19q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zm3-2h5q.425 0 .713-.288T14 16t-.288-.712T13 15H8q-.425 0-.712.288T7 16t.288.713T8 17m0-4h8q.425 0 .713-.288T17 12t-.288-.712T16 11H8q-.425 0-.712.288T7 12t.288.713T8 13m0-4h8q.425 0 .713-.288T17 8t-.288-.712T16 7H8q-.425 0-.712.288T7 8t.288.713T8 9m4.538-4.962q.212-.213.212-.538t-.213-.537T12 2.75t-.537.213t-.213.537t.213.538t.537.212t.538-.213M5 19V5z\"/>"
},
"attachment-rounded": {
"body": "<path fill=\"currentColor\" d=\"M8.25 18q-2.6 0-4.425-1.825T2 11.75t1.825-4.425T8.25 5.5h9.25q1.875 0 3.188 1.313T22 10t-1.312 3.188T17.5 14.5H8.75q-1.15 0-1.95-.8T6 11.75t.8-1.95T8.75 9H17q.425 0 .713.288T18 10t-.288.713T17 11H8.75q-.325 0-.537.213T8 11.75t.213.538t.537.212h8.75q1.05-.025 1.775-.737T20 10t-.725-1.775T17.5 7.5H8.25q-1.775-.025-3.012 1.225T4 11.75q0 1.75 1.238 2.975T8.25 16H17q.425 0 .713.288T18 17t-.288.713T17 18z\"/>"
},
"auto-awesome-rounded": {
"body": "<path fill=\"currentColor\" d=\"M19 8.3q-.125 0-.263-.075T18.55 8l-.8-1.75l-1.75-.8q-.15-.05-.225-.187T15.7 5q0-.125.075-.262T16 4.55l1.75-.8l.8-1.75q.05-.15.188-.225T19 1.7q.125 0 .263.075T19.45 2l.8 1.75l1.75.8q.15.05.225.188T22.3 5q0 .125-.075.263T22 5.45l-1.75.8l-.8 1.75q-.05.15-.188.225T19 8.3Zm0 14q-.125 0-.263-.075T18.55 22l-.8-1.75l-1.75-.8q-.15-.05-.225-.188T15.7 19q0-.125.075-.263T16 18.55l1.75-.8l.8-1.75q.05-.15.188-.225T19 15.7q.125 0 .263.075t.187.225l.8 1.75l1.75.8q.15.05.225.188T22.3 19q0 .125-.075.263T22 19.45l-1.75.8l-.8 1.75q-.05.15-.188.225T19 22.3ZM9 18.575q-.275 0-.525-.15T8.1 18l-1.6-3.5L3 12.9q-.275-.125-.425-.375T2.425 12q0-.275.15-.525T3 11.1l3.5-1.6L8.1 6q.125-.275.375-.425T9 5.425q.275 0 .525.15T9.9 6l1.6 3.5l3.5 1.6q.275.125.425.375t.15.525q0 .275-.15.525T15 12.9l-3.5 1.6L9.9 18q-.125.275-.375.425t-.525.15Z\"/>",
"hidden": true
},
"automation-outline": {
"body": "<path fill=\"currentColor\" d=\"M7.4 17.25q-1.05.875-2.187.8t-1.988-.775t-1.162-1.837t.412-2.338L4.35 10q-.625-.55-.987-1.325T3 7q0-1.65 1.175-2.825T7 3t2.825 1.175T11 7T9.825 9.825T7 11q-.225 0-.45-.025t-.425-.075L4.2 14.15q-.275.45-.175.888t.425.712t.775.313t.875-.313l10.5-9.025q1.05-.875 2.2-.788t2 .788t1.15 1.838t-.425 2.337L19.65 14q.625.55.988 1.325T21 17q0 1.65-1.175 2.825T17 21t-2.825-1.175T13 17t1.175-2.825T17 13q.225 0 .438.025t.412.075l1.95-3.25q.275-.45.175-.888t-.425-.712t-.775-.312t-.875.312zm1.013-8.837Q9 7.825 9 7t-.587-1.412T7 5t-1.412.588T5 7t.588 1.413T7 9t1.413-.587m10 10Q19 17.825 19 17t-.587-1.412T17 15t-1.412.588T15 17t.588 1.413T17 19t1.413-.587M17 17\"/>"
},
"backup": {
"body": "<path fill=\"currentColor\" d=\"M11 20H6.5q-2.275 0-3.887-1.575T1 14.575q0-1.95 1.175-3.475T5.25 9.15q.625-2.3 2.5-3.725T12 4q2.925 0 4.963 2.038T19 11q1.725.2 2.863 1.488T23 15.5q0 1.875-1.312 3.188T18.5 20H13v-7.15l1.6 1.55L16 13l-4-4l-4 4l1.4 1.4l1.6-1.55z\"/>"
},
"bookmark-add-rounded": {
"body": "<path fill=\"currentColor\" d=\"M17 7h-1q-.425 0-.712-.288T15 6t.288-.712T16 5h1V4q0-.425.288-.712T18 3t.713.288T19 4v1h1q.425 0 .713.288T21 6t-.288.713T20 7h-1v1q0 .425-.288.713T18 9t-.712-.288T17 8zm-5 11l-4.2 1.8q-1 .425-1.9-.162T5 17.975V5q0-.825.588-1.412T7 3h5.625q.45 0 .675.4t.025.825q-.175.425-.25.85T13 6q0 1.8 1.138 3.175T17 10.9q.3.05.538.063t.462.012q.425 0 .713.263T19 11.9v6.075q0 1.075-.9 1.663t-1.9.162z\"/>"
},
"branding-watermark-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11 17h7q.425 0 .713-.288T19 16v-4q0-.425-.288-.712T18 11h-7q-.425 0-.712.288T10 12v4q0 .425.288.713T11 17m-7 3q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm0-2h16V6H4zm0 0V6z\"/>"
},
"brush": {
"body": "<path fill=\"currentColor\" d=\"M6 21q-1.125 0-2.225-.55T2 19q.65 0 1.325-.513T4 17q0-1.25.875-2.125T7 14t2.125.875T10 17q0 1.65-1.175 2.825T6 21m5.75-6L9 12.25l8.95-8.95q.275-.275.688-.288t.712.288l1.35 1.35q.3.3.3.7t-.3.7z\"/>"
},
"build-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M9 15q-2.5 0-4.25-1.75T3 9q0-.5.075-1t.275-.95q.125-.25.313-.375t.412-.175t.463.013t.437.262L7.6 9.4l1.8-1.8l-2.625-2.625q-.2-.2-.262-.437T6.5 4.075t.175-.413t.375-.312q.45-.2.95-.275T9 3q2.5 0 4.25 1.75T15 9q0 .575-.1 1.088t-.3 1.012l5.05 5q.725.725.725 1.775t-.725 1.775t-1.775.725t-1.775-.75l-5-5.025q-.5.2-1.012.3T9 15m0-2q.65 0 1.3-.2t1.175-.625l6.075 6.075q.125.125.338.113t.337-.138t.125-.337t-.125-.338L12.15 11.5q.45-.5.65-1.162T13 9q0-1.5-.962-2.613T9.65 5.05L11.5 6.9q.3.3.3.7t-.3.7l-3.2 3.2q-.3.3-.7.3t-.7-.3L5.05 9.65q.225 1.425 1.338 2.388T9 13m2.725-1.275\"/>"
},
"change-history": {
"body": "<path fill=\"currentColor\" d=\"M2 20L12 4l10 16z\"/>"
},
"check": {
"body": "<path fill=\"currentColor\" d=\"m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z\"/>"
},
"check-circle": {
"body": "<path fill=\"currentColor\" d=\"m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"check-circle-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
},
"check-circle-rounded": {
"body": "<path fill=\"currentColor\" d=\"m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"chevron-left": {
"body": "<path fill=\"currentColor\" d=\"m14 18l-6-6l6-6l1.4 1.4l-4.6 4.6l4.6 4.6z\"/>"
},
"chevron-left-rounded": {
"body": "<path fill=\"currentColor\" d=\"m10.8 12l3.9 3.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-4.6-4.6q-.15-.15-.212-.325T8.425 12t.063-.375t.212-.325l4.6-4.6q.275-.275.7-.275t.7.275t.275.7t-.275.7z\"/>"
},
"chevron-right-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z\"/>"
},
"cleaning-services-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 23q-.825 0-1.412-.587T3 21v-5q0-2.075 1.463-3.537T8 11h1V3q0-.825.588-1.412T11 1h2q.825 0 1.413.588T15 3v8h1q2.075 0 3.538 1.463T21 16v5q0 .825-.587 1.413T19 23zm0-2h2v-3q0-.425.288-.712T8 17t.713.288T9 18v3h2v-3q0-.425.288-.712T12 17t.713.288T13 18v3h2v-3q0-.425.288-.712T16 17t.713.288T17 18v3h2v-5q0-1.25-.875-2.125T16 13H8q-1.25 0-2.125.875T5 16zm8-10V3h-2v8zm0 0h-2z\"/>"
},
"close": {
"body": "<path fill=\"currentColor\" d=\"M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z\"/>"
},
"close-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z\"/>"
},
"comment": {
"body": "<path fill=\"currentColor\" d=\"M6 14h12v-2H6zm0-3h12V9H6zm0-3h12V6H6zM4 18q-.825 0-1.412-.587T2 16V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v18l-4-4z\"/>"
},
"compare-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10 21H5q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h5V2q0-.425.288-.712T11 1t.713.288T12 2v20q0 .425-.288.713T11 23t-.712-.288T10 22zm-5-3h5v-6zm9 3v-9l5 6V5h-5V3h5q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
},
"compress": {
"body": "<path fill=\"currentColor\" d=\"M4 14v-2h16v2zm0-3V9h16v2zm7 11v-3.2l-1.6 1.6L8 19l4-4l4 4l-1.4 1.4l-1.6-1.55V22zm1-14L8 4l1.4-1.4L11 4.2V1h2v3.2l1.6-1.6L16 4z\"/>"
},
"content-copy": {
"body": "<path fill=\"currentColor\" d=\"M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm-4 4q-.825 0-1.412-.587T3 20V6h2v14h11v2z\"/>"
},
"content-copy-rounded": {
"body": "<path fill=\"currentColor\" d=\"M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm-4 4q-.825 0-1.412-.587T3 20V7q0-.425.288-.712T4 6t.713.288T5 7v13h10q.425 0 .713.288T16 21t-.288.713T15 22z\"/>"
},
"content-cut-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 14l-2.35 2.35q.2.375.275.8T10 18q0 1.65-1.175 2.825T6 22t-2.825-1.175T2 18t1.175-2.825T6 14q.425 0 .85.075t.8.275L10 12L7.65 9.65q-.375.2-.8.275T6 10q-1.65 0-2.825-1.175T2 6t1.175-2.825T6 2t2.825 1.175T10 6q0 .425-.075.85t-.275.8L20.6 18.6q.675.675.3 1.538T19.575 21q-.275 0-.537-.112t-.463-.313zm3-3l-2-2l5.575-5.575q.2-.2.463-.312T19.574 3q.95 0 1.313.875t-.313 1.55zM7.413 7.413Q8 6.825 8 6t-.587-1.412T6 4t-1.412.588T4 6t.588 1.413T6 8t1.413-.587m4.937 4.937q.15-.15.15-.35t-.15-.35t-.35-.15t-.35.15t-.15.35t.15.35t.35.15t.35-.15m-4.937 7.063Q8 18.825 8 18t-.587-1.412T6 16t-1.412.588T4 18t.588 1.413T6 20t1.413-.587\"/>"
},
"content-paste-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4.175q.275-.875 1.075-1.437T12 1q1 0 1.788.563T14.85 3H19q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5h-2v2q0 .425-.288.713T16 8H8q-.425 0-.712-.288T7 7V5H5zm7.713-14.288Q13 4.425 13 4t-.288-.712T12 3t-.712.288T11 4t.288.713T12 5t.713-.288\"/>"
},
"credit-card": {
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm0-8h16V8H4z\"/>"
},
"crop-free-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19v-3q0-.425.288-.712T4 15t.713.288T5 16v3h3q.425 0 .713.288T9 20t-.288.713T8 21zm14 0h-3q-.425 0-.712-.288T15 20t.288-.712T16 19h3v-3q0-.425.288-.712T20 15t.713.288T21 16v3q0 .825-.587 1.413T19 21M3 8V5q0-.825.588-1.412T5 3h3q.425 0 .713.288T9 4t-.288.713T8 5H5v3q0 .425-.288.713T4 9t-.712-.288T3 8m16 0V5h-3q-.425 0-.712-.288T15 4t.288-.712T16 3h3q.825 0 1.413.588T21 5v3q0 .425-.288.713T20 9t-.712-.288T19 8\"/>"
},
"crop-rounded": {
"body": "<path fill=\"currentColor\" d=\"M17 22v-3H7q-.825 0-1.412-.587T5 17V7H2q-.425 0-.712-.288T1 6t.288-.712T2 5h3V2q0-.425.288-.712T6 1t.713.288T7 2v15h15q.425 0 .713.288T23 18t-.288.713T22 19h-3v3q0 .425-.288.713T18 23t-.712-.288T17 22m0-7V7H9V5h8q.825 0 1.413.588T19 7v8z\"/>"
},
"square-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
},
"crop-square-outline": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm0 0V5v14Z\"/>",
"hidden": true
},
"dashboard-customize-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 11q-.425 0-.712-.288T3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11zm1-6v4zm9 6q-.425 0-.712-.288T13 10V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11zm1-6v4zM4 21q-.425 0-.712-.288T3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21zm1-6v4zm12 6q-.425 0-.712-.288T16 20v-2h-2.025q-.425 0-.7-.288T13 17t.288-.712T14 16h2v-2.025q0-.425.288-.7T17 13t.713.288T18 14v2h2.025q.425 0 .7.288T21 17t-.288.713T20 18h-2v2.025q0 .425-.288.7T17 21M15 5v4h4V5zM5 5v4h4V5zm0 10v4h4v-4z\"/>"
},
"dashboard-customize-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 11q-.425 0-.712-.288T3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11zm10 0q-.425 0-.712-.288T13 10V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11zM4 21q-.425 0-.712-.288T3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21zm13 0q-.425 0-.712-.288T16 20v-2h-2.025q-.425 0-.7-.288T13 17t.288-.712T14 16h2v-2.025q0-.425.288-.7T17 13t.713.288T18 14v2h2.025q.425 0 .7.288T21 17t-.288.713T20 18h-2v2.025q0 .425-.288.7T17 21\"/>"
},
"dashboard-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13 8V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v4q0 .425-.288.713T20 9h-6q-.425 0-.712-.288T13 8M3 12V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v8q0 .425-.288.713T10 13H4q-.425 0-.712-.288T3 12m10 8v-8q0-.425.288-.712T14 11h6q.425 0 .713.288T21 12v8q0 .425-.288.713T20 21h-6q-.425 0-.712-.288T13 20M3 20v-4q0-.425.288-.712T4 15h6q.425 0 .713.288T11 16v4q0 .425-.288.713T10 21H4q-.425 0-.712-.288T3 20m2-9h4V5H5zm10 8h4v-6h-4zm0-12h4V5h-4zM5 19h4v-2H5zm4-2\"/>"
},
"delete": {
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z\"/>"
},
"delete-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6q-.425 0-.712-.288T4 5t.288-.712T5 4h4q0-.425.288-.712T10 3h4q.425 0 .713.288T15 4h4q.425 0 .713.288T20 5t-.288.713T19 6v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zm-6.287 10.713Q11 16.425 11 16V9q0-.425-.288-.712T10 8t-.712.288T9 9v7q0 .425.288.713T10 17t.713-.288m4 0Q15 16.426 15 16V9q0-.425-.288-.712T14 8t-.712.288T13 9v7q0 .425.288.713T14 17t.713-.288M7 6v13z\"/>"
},
"delete-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6q-.425 0-.712-.288T4 5t.288-.712T5 4h4q0-.425.288-.712T10 3h4q.425 0 .713.288T15 4h4q.425 0 .713.288T20 5t-.288.713T19 6v13q0 .825-.587 1.413T17 21zm3.713-4.288Q11 16.426 11 16V9q0-.425-.288-.712T10 8t-.712.288T9 9v7q0 .425.288.713T10 17t.713-.288m4 0Q15 16.426 15 16V9q0-.425-.288-.712T14 8t-.712.288T13 9v7q0 .425.288.713T14 17t.713-.288\"/>"
},
"description": {
"body": "<path fill=\"currentColor\" d=\"M8 18h8v-2H8zm0-4h8v-2H8zm-2 8q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13h5l-5-5z\"/>"
},
"desktop-cloud-rounded": {
"body": "<path fill=\"currentColor\" d=\"M9 14h6.5q1.05 0 1.775-.737T18 11.474T17.25 9.7t-1.8-.725q-.2-1.275-1.175-2.125T12 6q-1.025 0-1.875.55T8.85 8.025q-1.2.05-2.025.913T6 11q0 1.25.875 2.125T9 14m1 5v-2H4q-.825 0-1.412-.587T2 15V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v10q0 .825-.587 1.413T20 17h-6v2h1q.425 0 .713.288T16 20t-.288.713T15 21H9q-.425 0-.712-.288T8 20t.288-.712T9 19z\"/>"
},
"download": {
"body": "<path fill=\"currentColor\" d=\"m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z\"/>"
},
"download-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11.625 15.513q-.175-.063-.325-.213l-3.6-3.6q-.3-.3-.288-.7t.288-.7q.3-.3.713-.312t.712.287L11 12.15V5q0-.425.288-.712T12 4t.713.288T13 5v7.15l1.875-1.875q.3-.3.713-.288t.712.313q.275.3.288.7t-.288.7l-3.6 3.6q-.15.15-.325.213t-.375.062t-.375-.062M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20z\"/>"
},
"edit": {
"body": "<path fill=\"currentColor\" d=\"M3 21v-4.25L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.438.65T21 6.4q0 .4-.137.763t-.438.662L7.25 21zM17.6 7.8L19 6.4L17.6 5l-1.4 1.4z\"/>"
},
"edit-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 19h1.425L16.2 9.225L14.775 7.8L5 17.575zm-1 2q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM19 6.4L17.6 5zm-3.525 2.125l-.7-.725L16.2 9.225z\"/>"
},
"edit-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 21q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM17.6 7.8L19 6.4L17.6 5l-1.4 1.4z\"/>"
},
"edit-square-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6.525q.5 0 .75.313t.25.687t-.262.688T11.5 5H5v14h14v-6.525q0-.5.313-.75t.687-.25t.688.25t.312.75V19q0 .825-.587 1.413T19 21zm4-7v-2.425q0-.4.15-.763t.425-.637l8.6-8.6q.3-.3.675-.45t.75-.15q.4 0 .763.15t.662.45L22.425 3q.275.3.425.663T23 4.4t-.137.738t-.438.662l-8.6 8.6q-.275.275-.637.438t-.763.162H10q-.425 0-.712-.288T9 14m12.025-9.6l-1.4-1.4zM11 13h1.4l5.8-5.8l-.7-.7l-.725-.7L11 11.575zm6.5-6.5l-.725-.7zl.7.7z\"/>"
},
"edit-square-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10 15q-.425 0-.712-.288T9 14v-2.425q0-.4.15-.763t.425-.637l8.6-8.6q.3-.3.675-.45t.75-.15q.4 0 .763.15t.662.45L22.425 3q.275.3.425.663T23 4.4t-.137.738t-.438.662l-8.6 8.6q-.275.275-.637.438t-.763.162zm9.6-9.2l1.425-1.4l-1.4-1.4L18.2 4.4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6.5q.35 0 .575.175t.35.45t.087.55t-.287.525l-4.65 4.65q-.275.275-.425.638T7 10.75V15q0 .825.588 1.412T9 17h4.225q.4 0 .763-.15t.637-.425L19.3 11.75q.25-.25.525-.288t.55.088t.45.35t.175.575V19q0 .825-.587 1.413T19 21z\"/>"
},
"error": {
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288M11 13h2V7h-2zm1 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"error-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288m0-4Q13 12.425 13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
},
"error-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288m0-4Q13 12.425 13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"expand-more-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12 14.95q-.2 0-.375-.062t-.325-.213l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213T12 14.95\"/>",
"hidden": true
},
"explore-rounded": {
"body": "<path fill=\"currentColor\" d=\"m8.375 16.25l5.05-1.45q.5-.15.863-.513t.512-.862l1.45-5.05q.075-.275-.137-.488t-.488-.137l-5.05 1.45q-.5.15-.862.513t-.513.862l-1.45 5.05q-.075.275.138.488t.487.137M12 13.5q-.625 0-1.062-.437T10.5 12t.438-1.062T12 10.5t1.063.438T13.5 12t-.437 1.063T12 13.5m0 8.5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"extension-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.413-.587Q3 19.825 3 19v-3.8q1.2 0 2.1-.762q.9-.763.9-1.938q0-1.175-.9-1.938Q4.2 9.8 3 9.8V6q0-.825.587-1.412Q4.175 4 5 4h4q0-1.05.725-1.775Q10.45 1.5 11.5 1.5q1.05 0 1.775.725Q14 2.95 14 4h4q.825 0 1.413.588Q20 5.175 20 6v4q1.05 0 1.775.725q.725.725.725 1.775q0 1.05-.725 1.775Q21.05 15 20 15v4q0 .825-.587 1.413Q18.825 21 18 21h-3.8q0-1.2-.762-2.1q-.763-.9-1.938-.9q-1.175 0-1.938.9q-.762.9-.762 2.1Z\"/>",
"hidden": true
},
"fact-check-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 21q-.825 0-1.412-.587T2 19V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v14q0 .825-.587 1.413T20 21zm0-2h16V5H4zm5-2q.425 0 .713-.288T10 16t-.288-.712T9 15H6q-.425 0-.712.288T5 16t.288.713T6 17zm5.55-4.825l-.725-.725q-.3-.3-.7-.287t-.7.312q-.275.3-.288.7t.288.7L13.85 14.3q.3.3.7.3t.7-.3l3.55-3.55q.3-.3.3-.7t-.3-.7t-.712-.3t-.713.3zM9 13q.425 0 .713-.288T10 12t-.288-.712T9 11H6q-.425 0-.712.288T5 12t.288.713T6 13zm0-4q.425 0 .713-.288T10 8t-.288-.712T9 7H6q-.425 0-.712.288T5 8t.288.713T6 9zM4 19V5z\"/>"
},
"fact-check-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 21q-.825 0-1.412-.587T2 19V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v14q0 .825-.587 1.413T20 21zm5-4q.425 0 .713-.288T10 16t-.288-.712T9 15H6q-.425 0-.712.288T5 16t.288.713T6 17zm5.55-4.825l-.725-.725q-.3-.3-.7-.287t-.7.312q-.275.3-.288.7t.288.7L13.85 14.3q.3.3.7.3t.7-.3l3.55-3.55q.3-.3.3-.7t-.3-.7t-.712-.3t-.713.3zM9 13q.425 0 .713-.288T10 12t-.288-.712T9 11H6q-.425 0-.712.288T5 12t.288.713T6 13zm0-4q.425 0 .713-.288T10 8t-.288-.712T9 7H6q-.425 0-.712.288T5 8t.288.713T6 9z\"/>"
},
"find-replace": {
"body": "<path fill=\"currentColor\" d=\"M4.1 10q.35-2.575 2.288-4.288T11 4q1.475 0 2.763.563T16 6.1V4h2v6h-6V8h3q-.725-.9-1.737-1.45T11 6Q9.2 6 7.825 7.138T6.1 10zm15.5 11l-4.4-4.4q-.9.675-1.962 1.038T11 18q-1.475 0-2.762-.562T6 15.9V18H4v-6h6v2H7q.725.9 1.738 1.45T11 16q1.8 0 3.175-1.137T15.9 12h2q-.125.9-.45 1.688T16.6 15.2l4.4 4.4z\"/>"
},
"folder-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h5.175q.4 0 .763.15t.637.425L12 6h8q.825 0 1.413.588T22 8v10q0 .825-.587 1.413T20 20z\"/>"
},
"format-align-center": {
"body": "<path fill=\"currentColor\" d=\"M3 21v-2h18v2zm4-4v-2h10v2zm-4-4v-2h18v2zm4-4V7h10v2zM3 5V3h18v2z\"/>"
},
"format-align-left": {
"body": "<path fill=\"currentColor\" d=\"M3 21v-2h18v2zm0-4v-2h12v2zm0-4v-2h18v2zm0-4V7h12v2zm0-4V3h18v2z\"/>"
},
"format-align-right": {
"body": "<path fill=\"currentColor\" d=\"M3 5V3h18v2zm6 4V7h12v2zm-6 4v-2h18v2zm6 4v-2h12v2zm-6 4v-2h18v2z\"/>"
},
"format-color-fill-rounded": {
"body": "<path fill=\"currentColor\" d=\"m8.3.7l7.875 7.875q.575.575.575 1.425t-.575 1.425l-4.75 4.75q-.575.575-1.425.575t-1.425-.575l-4.75-4.75Q3.25 10.85 3.25 10t.575-1.425L8.575 3.8l-1.7-1.7q-.3-.3-.288-.7T6.9.7q.3-.275.7-.287T8.3.7M10 5.225L5.225 10h9.55zm7.588 11.188Q17 15.825 17 15q0-.525.313-1.125T18 12.75q.225-.3.475-.625T19 11.5q.275.3.525.625t.475.625q.375.525.688 1.125T21 15q0 .825-.587 1.413T19 17t-1.412-.587M4 24q-.825 0-1.412-.587T2 22t.588-1.412T4 20h16q.825 0 1.413.588T22 22t-.587 1.413T20 24z\"/>"
},
"format-underlined": {
"body": "<path fill=\"currentColor\" d=\"M5 21v-2h14v2zm3.075-5.575q-1.4-1.575-1.4-4.175V3H9.25v8.4q0 1.4.7 2.275t2.05.875t2.05-.875t.7-2.275V3h2.575v8.25q0 2.6-1.4 4.175T12 17t-3.925-1.575\"/>"
},
"gavel-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 19h10q.425 0 .713.288T16 20t-.288.713T15 21H5q-.425 0-.712-.288T4 20t.288-.712T5 19m3.225-4.275L5.4 11.9q-.575-.575-.587-1.412t.562-1.413L6.1 8.35L11.8 14l-.725.725q-.575.575-1.425.575t-1.425-.575M16 9.8l-5.65-5.7l.725-.725q.575-.575 1.413-.562T13.9 3.4l2.825 2.825q.575.575.575 1.425t-.575 1.425zm3.9 9.5L7.55 6.95l1.4-1.4L21.3 17.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275\"/>"
},
"gpp-maybe-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.713 15.713Q13 15.425 13 15t-.288-.712T12 14t-.712.288T11 15t.288.713T12 16t.713-.288m0-4Q13 11.426 13 11V8q0-.425-.288-.712T12 7t-.712.288T11 8v3q0 .425.288.713T12 12t.713-.288m-1.038 10.163q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025M12 19.9q2.6-.825 4.3-3.3t1.7-5.5V6.375l-6-2.25l-6 2.25V11.1q0 3.025 1.7 5.5t4.3 3.3m0-7.9\"/>"
},
"group": {
"body": "<path fill=\"currentColor\" d=\"M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20zm18 0v-3q0-1.1-.612-2.113T16.65 13.15q1.275.15 2.4.513t2.1.887q.9.5 1.375 1.112T23 17v3zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175m11.65 0Q16.65 12 15 12q-.275 0-.7-.062t-.7-.138q.675-.8 1.038-1.775T15 8t-.362-2.025T13.6 4.2q.35-.125.7-.163T15 4q1.65 0 2.825 1.175T19 8t-1.175 2.825\"/>"
},
"group-add": {
"body": "<path fill=\"currentColor\" d=\"M12.5 11.95q.725-.8 1.113-1.825T14 8t-.387-2.125T12.5 4.05q1.5.2 2.5 1.325T16 8t-1 2.625t-2.5 1.325M18 20v-3q0-.9-.4-1.713t-1.05-1.437q1.275.45 2.363 1.163T20 17v3zm2-7v-2h-2V9h2V7h2v2h2v2h-2v2zM5.175 10.825Q4 9.65 4 8t1.175-2.825T8 4t2.825 1.175T12 8t-1.175 2.825T8 12t-2.825-1.175M0 20v-2.8q0-.85.438-1.562T1.6 14.55q1.55-.775 3.15-1.162T8 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T16 17.2V20z\"/>"
},
"group-rounded": {
"body": "<path fill=\"currentColor\" d=\"M1 17.2q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2v.8q0 .825-.587 1.413T15 20H3q-.825 0-1.412-.587T1 18zM18.45 20q.275-.45.413-.962T19 18v-1q0-1.1-.612-2.113T16.65 13.15q1.275.15 2.4.513t2.1.887q.9.5 1.375 1.112T23 17v1q0 .825-.587 1.413T21 20zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175m11.65 0Q16.65 12 15 12q-.275 0-.7-.062t-.7-.138q.675-.8 1.038-1.775T15 8t-.362-2.025T13.6 4.2q.35-.125.7-.163T15 4q1.65 0 2.825 1.175T19 8t-1.175 2.825\"/>"
},
"groups-rounded": {
"body": "<path fill=\"currentColor\" d=\"M1 18q-.425 0-.712-.288T0 17v-.575q0-1.075 1.1-1.75T4 14q.325 0 .625.013t.575.062q-.35.525-.525 1.1t-.175 1.2V18zm6 0q-.425 0-.712-.288T6 17v-.625q0-.8.438-1.463t1.237-1.162T9.588 13T12 12.75q1.325 0 2.438.25t1.912.75t1.225 1.163t.425 1.462V17q0 .425-.287.713T17 18zm12.5 0v-1.625q0-.65-.162-1.225t-.488-1.075q.275-.05.563-.062T20 14q1.8 0 2.9.663t1.1 1.762V17q0 .425-.288.713T23 18zM4 13q-.825 0-1.412-.587T2 11q0-.85.588-1.425T4 9q.85 0 1.425.575T6 11q0 .825-.575 1.413T4 13m16 0q-.825 0-1.412-.587T18 11q0-.85.588-1.425T20 9q.85 0 1.425.575T22 11q0 .825-.575 1.413T20 13m-8-1q-1.25 0-2.125-.875T9 9q0-1.275.875-2.137T12 6q1.275 0 2.138.863T15 9q0 1.25-.862 2.125T12 12\"/>"
},
"help-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.838 17.638q.362-.363.362-.888t-.362-.888t-.888-.362t-.887.363t-.363.887t.363.888t.887.362t.888-.363M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m.1-14.3q.625 0 1.088.4t.462 1q0 .55-.337.975t-.763.8q-.575.5-1.012 1.1t-.438 1.35q0 .35.263.588t.612.237q.375 0 .638-.25t.337-.625q.1-.525.45-.937t.75-.788q.575-.55.988-1.2t.412-1.45q0-1.275-1.037-2.087T12.1 6q-.95 0-1.812.4T8.975 7.625q-.175.3-.112.638t.337.512q.35.2.725.125t.625-.425q.275-.375.688-.575t.862-.2\"/>"
},
"highlight": {
"body": "<path fill=\"currentColor\" d=\"M5.65 8L3.5 5.9l1.4-1.45L7.05 6.6zM11 5V2h2v3zm7.4 3l-1.45-1.4l2.15-2.1l1.4 1.4zM9 22v-5l-3-3V9h12v5l-3 3v5z\"/>"
},
"image-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zm0 0V5zm2-2h10q.3 0 .45-.275t-.05-.525l-2.75-3.675q-.15-.2-.4-.2t-.4.2L11.25 16L9.4 13.525q-.15-.2-.4-.2t-.4.2l-2 2.675q-.2.25-.05.525T7 17\"/>"
},
"info": {
"body": "<path fill=\"currentColor\" d=\"M11 17h2v-6h-2zm1.713-8.287Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"info-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17t.713-.288m0-8Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
},
"info-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17t.713-.288m0-8Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"javascript-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7.5 15q-.625 0-1.062-.437T6 13.5v-.25q0-.325.213-.537t.537-.213t.538.213t.212.537v.25H9V9.75q0-.325.213-.537T9.75 9t.538.213t.212.537v3.75q0 .625-.437 1.063T9 15zm5.5 0q-.425 0-.712-.288T12 14v-.5q0-.2.15-.35t.35-.15h.5q.2 0 .35.15t.15.35h2v-1H13q-.425 0-.712-.288T12 11.5V10q0-.425.288-.712T13 9h3q.425 0 .713.288T17 10v.5q0 .2-.15.35t-.35.15H16q-.2 0-.35-.15t-.15-.35h-2v1H16q.425 0 .713.288T17 12.5V14q0 .425-.288.713T16 15z\"/>"
},
"key": {
"body": "<path fill=\"currentColor\" d=\"M9.125 14.125Q10 13.25 10 12t-.875-2.125T7 9t-2.125.875T4 12t.875 2.125T7 15t2.125-.875M7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.025 0 3.538 1.15T12.65 10h8.375L23 11.975l-3.5 4L17 14l-2 2l-2-2h-.35q-.625 1.8-2.175 2.9T7 18\"/>"
},
"key-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4.875 14.125Q4 13.25 4 12t.875-2.125T7 9t2.125.875T10 12t-.875 2.125T7 15t-2.125-.875M7 18q1.925 0 3.475-1.1T12.65 14H13l1.3 1.3q.15.15.325.212t.375.063t.375-.062t.325-.213L17 14l1.75 1.375q.15.125.338.187t.387.038t.363-.112t.287-.238l2.25-2.575q.125-.15.188-.325t.062-.375t-.075-.362t-.2-.288L21.325 10.3q-.15-.15-.337-.225T20.6 10h-7.95q-.6-1.7-2.113-2.85T7 6Q4.5 6 2.75 7.75T1 12t1.75 4.25T7 18\"/>"
},
"keyboard-arrow-down": {
"body": "<path fill=\"currentColor\" d=\"m12 15.4l-6-6L7.4 8l4.6 4.6L16.6 8L18 9.4z\"/>"
},
"keyboard-arrow-up": {
"body": "<path fill=\"currentColor\" d=\"m12 10.8l-4.6 4.6L6 14l6-6l6 6l-1.4 1.4z\"/>"
},
"keyboard-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 19q-.825 0-1.412-.587T2 17V7q0-.825.588-1.412T4 5h16q.825 0 1.413.588T22 7v10q0 .825-.587 1.413T20 19zm5-3h6q.425 0 .713-.288T16 15t-.288-.712T15 14H9q-.425 0-.712.288T8 15t.288.713T9 16M6.713 9.712Q7 9.425 7 9t-.288-.712T6 8t-.712.288T5 9t.288.713T6 10t.713-.288m3 0Q10 9.426 10 9t-.288-.712T9 8t-.712.288T8 9t.288.713T9 10t.713-.288m3 0Q13 9.426 13 9t-.288-.712T12 8t-.712.288T11 9t.288.713T12 10t.713-.288m3 0Q16 9.426 16 9t-.288-.712T15 8t-.712.288T14 9t.288.713T15 10t.713-.288m3 0Q19 9.426 19 9t-.288-.712T18 8t-.712.288T17 9t.288.713T18 10t.713-.288m-12 3Q7 12.426 7 12t-.288-.712T6 11t-.712.288T5 12t.288.713T6 13t.713-.288m3 0Q10 12.426 10 12t-.288-.712T9 11t-.712.288T8 12t.288.713T9 13t.713-.288m3 0Q13 12.426 13 12t-.288-.712T12 11t-.712.288T11 12t.288.713T12 13t.713-.288m3 0Q16 12.426 16 12t-.288-.712T15 11t-.712.288T14 12t.288.713T15 13t.713-.288m3 0Q19 12.426 19 12t-.288-.712T18 11t-.712.288T17 12t.288.713T18 13t.713-.288\"/>"
},
"language": {
"body": "<path fill=\"currentColor\" d=\"M8.125 21.213q-1.825-.788-3.187-2.15t-2.15-3.188T2 11.988t.788-3.875t2.15-3.175t3.187-2.15T12.013 2t3.875.788t3.175 2.15t2.15 3.175t.787 3.875t-.787 3.887t-2.15 3.188t-3.175 2.15t-3.875.787t-3.888-.787M12 19.95q.65-.9 1.125-1.875T13.9 16h-3.8q.3 1.1.775 2.075T12 19.95m-2.6-.4q-.45-.825-.787-1.713T8.05 16H5.1q.725 1.25 1.813 2.175T9.4 19.55m5.2 0q1.4-.45 2.488-1.375T18.9 16h-2.95q-.225.95-.562 1.838T14.6 19.55M4.25 14h3.4q-.075-.5-.112-.987T7.5 12t.038-1.012T7.65 10h-3.4q-.125.5-.187.988T4 12t.063 1.013t.187.987m5.4 0h4.7q.075-.5.113-.987T14.5 12t-.038-1.012T14.35 10h-4.7q-.075.5-.112.988T9.5 12t.038 1.013t.112.987m6.7 0h3.4q.125-.5.188-.987T20 12t-.062-1.012T19.75 10h-3.4q.075.5.113.988T16.5 12t-.038 1.013t-.112.987m-.4-6h2.95q-.725-1.25-1.812-2.175T14.6 4.45q.45.825.788 1.713T15.95 8M10.1 8h3.8q-.3-1.1-.775-2.075T12 4.05q-.65.9-1.125 1.875T10.1 8m-5 0h2.95q.225-.95.563-1.838T9.4 4.45Q8 4.9 6.912 5.825T5.1 8\"/>"
},
"layers-clear-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M18.975 7.425q.775.6.775 1.575t-.775 1.575L16.8 12.25q-.3.225-.675.2t-.65-.3q-.325-.325-.288-.763t.388-.712L17.75 9L12 4.55L10.35 5.8q-.3.225-.675.2t-.65-.3q-.325-.325-.288-.763t.388-.712l1.65-1.275q.55-.425 1.225-.425t1.225.425zM19.1 21.4l-3.3-3.3l-2.575 2q-.55.425-1.225.425t-1.225-.425l-6.75-5.25q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l2.35-1.825l-1.75-1.7h.725l-.1.05q-.55.425-1.225.438t-1.225-.413l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l.05-.05L2.1 4.425q-.3-.3-.312-.712T2.075 3t.713-.3t.712.3l17 17q.275.275.275.7t-.275.7t-.7.275t-.7-.275m.875-8.125q.375.3.375.775t-.375.8l-.3.25q-.3.25-.675.225t-.65-.3q-.325-.325-.3-.788t.4-.737l.3-.225q.275-.2.613-.2t.612.2\"/>"
},
"layers-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4.025 14.85q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l6.75-5.225q.275-.2.6-.2t.6.2q.4.3.413.787t-.388.788l-6.75 5.25q-.55.425-1.225.425t-1.225-.425zm6.75.2l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l5.75-4.475q.55-.425 1.225-.425t1.225.425l5.75 4.475q.775.6.775 1.575t-.775 1.575l-5.75 4.475q-.55.425-1.225.425t-1.225-.425M12 13.45L17.75 9L12 4.55L6.25 9zM12 9\"/>"
},
"library-add-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M14.713 13.713Q15 13.425 15 13v-2h2q.425 0 .713-.288T18 10t-.288-.712T17 9h-2V7q0-.425-.288-.712T14 6t-.712.288T13 7v2h-2q-.425 0-.712.288T10 10t.288.713T11 11h2v2q0 .425.288.713T14 14t.713-.288M8 18q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm0-2h12V4H8zm-4 6q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6t.713.288T4 7v13h13q.425 0 .713.288T18 21t-.288.713T17 22zM8 4v12z\"/>"
},
"link": {
"body": "<path fill=\"currentColor\" d=\"M11 17H7q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h4v2H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h4zm-3-4v-2h8v2zm5 4v-2h4q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-4V7h4q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z\"/>"
},
"link-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7 17q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h3q.425 0 .713.288T11 8t-.288.713T10 9H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h3q.425 0 .713.288T11 16t-.288.713T10 17zm2-4q-.425 0-.712-.288T8 12t.288-.712T9 11h6q.425 0 .713.288T16 12t-.288.713T15 13zm5 4q-.425 0-.712-.288T13 16t.288-.712T14 15h3q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-3q-.425 0-.712-.288T13 8t.288-.712T14 7h3q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z\"/>"
},
"lock": {
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm7.413-5.587Q14 15.825 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6z\"/>"
},
"lock-open": {
"body": "<path fill=\"currentColor\" d=\"M6 8h9V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6H7q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22H6q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8m7.413 8.413Q14 15.825 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587\"/>"
},
"lock-open-right-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M6 20h12V10H6zm7.413-3.588Q14 15.826 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M6 20V10zm0 2q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h7V6q0-2.075 1.463-3.537T18 1q1.775 0 3.1 1.075t1.75 2.7q.125.425-.162.825T22 6q-.425 0-.7-.175t-.4-.575q-.275-.95-1.062-1.6T18 3q-1.25 0-2.125.875T15 6v2h3q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22z\"/>"
},
"lock-outline": {
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm0-2h12V10H6zm7.413-3.588Q14 15.826 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6zM6 20V10z\"/>"
},
"login": {
"body": "<path fill=\"currentColor\" d=\"M12 21v-2h7V5h-7V3h7q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm-2-4l-1.375-1.45l2.55-2.55H3v-2h8.175l-2.55-2.55L10 7l5 5z\"/>"
},
"logout": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h7v2H5v14h7v2zm11-4l-1.375-1.45l2.55-2.55H9v-2h8.175l-2.55-2.55L16 7l5 5z\"/>"
},
"logout-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6q.425 0 .713.288T12 4t-.288.713T11 5H5v14h6q.425 0 .713.288T12 20t-.288.713T11 21zm12.175-8H10q-.425 0-.712-.288T9 12t.288-.712T10 11h7.175L15.3 9.125q-.275-.275-.275-.675t.275-.7t.7-.313t.725.288L20.3 11.3q.3.3.3.7t-.3.7l-3.575 3.575q-.3.3-.712.288t-.713-.313q-.275-.3-.262-.712t.287-.688z\"/>"
},
"looks-one-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12 9v7q0 .425.288.713T13 17t.713-.288T14 16V8q0-.425-.288-.712T13 7h-2q-.425 0-.712.288T10 8t.288.713T11 9zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14z\"/>"
},
"mail-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm8.263-7.212q.137-.038.262-.113L19.6 8.25q.2-.125.3-.312t.1-.413q0-.5-.425-.75T18.7 6.8L12 11L5.3 6.8q-.45-.275-.875-.012T4 7.525q0 .25.1.438t.3.287l7.075 4.425q.125.075.263.113t.262.037t.263-.037\"/>"
},
"match-word-rounded": {
"body": "<path fill=\"currentColor\" d=\"M3 19.025q-.825 0-1.412-.587T1 17.025v-2q0-.425.288-.712T2 14.025t.713.288t.287.712v2h18v-2q0-.425.288-.713t.712-.287t.713.288t.287.712v2q0 .825-.587 1.413T21 19.025zm4.35-3.8q-1.225 0-1.925-.637t-.7-1.738q0-1.05.813-1.712t2.087-.663q.575 0 1.063.088t.837.287v-.35q0-.675-.462-1.075t-1.263-.4q-.375 0-.712.113t-.613.312q-.225.175-.487.2T5.5 9.5t-.275-.437t.15-.463q.45-.425 1.063-.65t1.387-.225q1.55 0 2.375.738t.825 2.137v3.675q0 .3-.213.513T10.3 15q-.325 0-.537-.213t-.213-.537v-.1h-.075q-.325.5-.875.788t-1.25.287m.55-3.575q-.8 0-1.225.313t-.425.887q0 .5.375.813t.975.312q.8 0 1.363-.562t.562-1.363q-.35-.2-.8-.3t-.825-.1m8.425 3.575q-1.025 0-1.562-.45t-.688-.7H14v.325q0 .3-.212.513t-.513.212t-.525-.225t-.225-.525V5.75q0-.325.225-.55t.55-.225t.55.225t.225.55V7.8L14 8.8h.075q.075-.125.6-.638t1.65-.512q1.6 0 2.525 1.15t.925 2.65t-.912 2.638t-2.538 1.137M16.1 9.05q-1 0-1.55.738T14 11.425q0 .925.55 1.65t1.55.725t1.563-.725t.562-1.65t-.562-1.65T16.1 9.05\"/>"
},
"menu-book-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M6.5 16q1.175 0 2.288.263T11 17.05V7.2q-1.025-.6-2.175-.9T6.5 6q-.9 0-1.788.175T3 6.7v9.9q.875-.3 1.738-.45T6.5 16m6.5 1.05q1.1-.525 2.213-.787T17.5 16q.9 0 1.763.15T21 16.6V6.7q-.825-.35-1.713-.525T17.5 6q-1.175 0-2.325.3T13 7.2zm-1.662 2.338q-.313-.088-.588-.238q-.975-.575-2.05-.862T6.5 18q-1.05 0-2.062.275T2.5 19.05q-.525.275-1.012-.025T1 18.15V6.1q0-.275.138-.525T1.55 5.2q1.15-.6 2.4-.9T6.5 4q1.45 0 2.838.375T12 5.5q1.275-.75 2.663-1.125T17.5 4q1.3 0 2.55.3t2.4.9q.275.125.413.375T23 6.1v12.05q0 .575-.487.875t-1.013.025q-.925-.5-1.937-.775T17.5 18q-1.125 0-2.2.288t-2.05.862q-.275.15-.587.238t-.663.087t-.663-.088M14 8.776q0-.225.163-.462T14.525 8q.725-.25 1.45-.375T17.5 7.5q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 9q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025T14 8.775m0 5.5q0-.225.163-.462t.362-.313q.725-.25 1.45-.375T17.5 13q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 14.5q-.65 0-1.275.113t-1.2.312q-.45.175-.737-.012T14 14.275m0-2.75q0-.225.163-.462t.362-.313q.725-.25 1.45-.375t1.525-.125q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112t-.788-.038q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025t-.288-.65\"/>"
},
"menu-book-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13 17.05q1.1-.525 2.213-.788T17.5 16q.9 0 1.763.15T21 16.6V6.7q-.825-.35-1.713-.525T17.5 6q-1.175 0-2.325.3T13 7.2zm-1.662 2.338q-.313-.088-.588-.238q-.975-.575-2.05-.862T6.5 18q-1.05 0-2.062.275T2.5 19.05q-.525.275-1.012-.025T1 18.15V6.1q0-.275.138-.525T1.55 5.2q1.175-.575 2.413-.888T6.5 4q1.45 0 2.838.375T12 5.5q1.275-.75 2.663-1.125T17.5 4q1.3 0 2.538.313t2.412.887q.275.125.413.375T23 6.1v12.05q0 .575-.487.875t-1.013.025q-.925-.5-1.937-.775T17.5 18q-1.125 0-2.2.288t-2.05.862q-.275.15-.587.238t-.663.087t-.663-.088M14 8.776q0-.225.163-.462T14.525 8q.725-.25 1.45-.375T17.5 7.5q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 9q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025T14 8.775m0 5.5q0-.225.163-.462t.362-.313q.725-.25 1.45-.375T17.5 13q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 14.5q-.65 0-1.275.113t-1.2.312q-.45.175-.737-.012T14 14.275m0-2.75q0-.225.163-.462t.362-.313q.725-.25 1.45-.375t1.525-.125q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112t-.788-.038q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025t-.288-.65\"/>"
},
"more-vert": {
"body": "<path fill=\"currentColor\" d=\"M12 20q-.825 0-1.412-.587T10 18t.588-1.412T12 16t1.413.588T14 18t-.587 1.413T12 20m0-6q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14m0-6q-.825 0-1.412-.587T10 6t.588-1.412T12 4t1.413.588T14 6t-.587 1.413T12 8\"/>"
},
"move-down-rounded": {
"body": "<path fill=\"currentColor\" d=\"M3 11q0 1.8 1.15 3.175T7.075 15.9l-.775-.775q-.275-.275-.275-.687t.275-.713q.3-.3.713-.3t.712.3L10.3 16.3q.3.3.3.7t-.3.7l-2.6 2.6q-.275.275-.7.275t-.725-.3Q6 20 6 19.6t.275-.7l.9-.95q-2.65-.3-4.412-2.287T1 11q0-2.925 2.038-4.962T8 4h2q.425 0 .713.288T11 5t-.288.713T10 6H8Q5.925 6 4.463 7.463T3 11m11 9q-.425 0-.712-.288T13 19v-5q0-.425.288-.712T14 13h7q.425 0 .713.288T22 14v5q0 .425-.288.713T21 20zm0-9q-.425 0-.712-.288T13 10V5q0-.425.288-.712T14 4h7q.425 0 .713.288T22 5v5q0 .425-.288.713T21 11zm1-2h5V6h-5z\"/>"
},
"opacity": {
"body": "<path fill=\"currentColor\" d=\"M6.338 18.7Q4 16.4 4 13.1q0-1.625.625-3.037T6.35 7.55L12 2l5.65 5.55q1.1 1.1 1.725 2.513T20 13.1q0 3.3-2.337 5.6T12 21t-5.663-2.3M6.05 14H17.9q.3-1.8-.337-3.075T16.25 9L12 4.8L7.75 9q-.675.65-1.325 1.925T6.05 14\"/>"
},
"open-in-new-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6q.425 0 .713.288T12 4t-.288.713T11 5H5v14h14v-6q0-.425.288-.712T20 12t.713.288T21 13v6q0 .825-.587 1.413T19 21zM19 6.4L10.4 15q-.275.275-.7.275T9 15t-.275-.7t.275-.7L17.6 5H15q-.425 0-.712-.288T14 4t.288-.712T15 3h5q.425 0 .713.288T21 4v5q0 .425-.288.713T20 10t-.712-.288T19 9z\"/>"
},
"palette-outline": {
"body": "<path fill=\"currentColor\" d=\"M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.813-3.9t2.2-3.175T8.25 2.788T12.2 2q2 0 3.775.688t3.113 1.9t2.125 2.875T22 11.05q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.312.125t-.088.275q0 .3.375.863t.375 1.287q0 1.25-.687 1.85T12 22m-4.425-9.425Q8 12.15 8 11.5t-.425-1.075T6.5 10t-1.075.425T5 11.5t.425 1.075T6.5 13t1.075-.425m3-4Q11 8.15 11 7.5t-.425-1.075T9.5 6t-1.075.425T8 7.5t.425 1.075T9.5 9t1.075-.425m5 0Q16 8.15 16 7.5t-.425-1.075T14.5 6t-1.075.425T13 7.5t.425 1.075T14.5 9t1.075-.425m3 4Q19 12.15 19 11.5t-.425-1.075T17.5 10t-1.075.425T16 11.5t.425 1.075T17.5 13t1.075-.425M12 20q.225 0 .363-.125t.137-.325q0-.35-.375-.825T11.75 17.3q0-1.05.725-1.675T14.25 15H16q1.65 0 2.825-.962T20 11.05q0-3.025-2.312-5.038T12.2 4Q8.8 4 6.4 6.325T4 12q0 3.325 2.338 5.663T12 20\"/>"
},
"pan-tool-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10.05 23q-.75 0-1.4-.337T7.575 21.7l-5.9-8.65q-.2-.3-.175-.65t.3-.6q.475-.475 1.125-.55t1.175.3L7 13.575V4q0-.425.288-.712T8 3t.713.288T9 4v8h2V2q0-.425.288-.712T12 1t.713.288T13 2v10h2V3q0-.425.288-.712T16 2t.713.288T17 3v9h2V5q0-.425.288-.712T20 4t.713.288T21 5v14q0 1.65-1.175 2.825T17 23z\"/>"
},
"password-rounded": {
"body": "<path fill=\"currentColor\" d=\"M3 17h18q.425 0 .713.288T22 18t-.288.713T21 19H3q-.425 0-.712-.288T2 18t.288-.712T3 17m1-5.55l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T1 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T7 9.95t-.213.538t-.537.212H5.3l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T9 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .537.213T15 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T17 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T23 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35z\"/>"
},
"pause-rounded": {
"body": "<path fill=\"currentColor\" d=\"M16 19q-.825 0-1.412-.587T14 17V7q0-.825.588-1.412T16 5t1.413.588T18 7v10q0 .825-.587 1.413T16 19m-8 0q-.825 0-1.412-.587T6 17V7q0-.825.588-1.412T8 5t1.413.588T10 7v10q0 .825-.587 1.413T8 19\"/>"
},
"person": {
"body": "<path fill=\"currentColor\" d=\"M9.175 10.825Q8 9.65 8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12t-2.825-1.175M4 20v-2.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2V20z\"/>"
},
"person-add": {
"body": "<path fill=\"currentColor\" d=\"M18 14v-3h-3V9h3V6h2v3h3v2h-3v3zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
},
"person-add-rounded": {
"body": "<path fill=\"currentColor\" d=\"M18 11h-2q-.425 0-.712-.288T15 10t.288-.712T16 9h2V7q0-.425.288-.712T19 6t.713.288T20 7v2h2q.425 0 .713.288T23 10t-.288.713T22 11h-2v2q0 .425-.288.713T19 14t-.712-.288T18 13zm-11.825-.175Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 18v-.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2v.8q0 .825-.587 1.413T15 20H3q-.825 0-1.412-.587T1 18\"/>"
},
"person-check": {
"body": "<path fill=\"currentColor\" d=\"M17.55 12L14 8.45l1.425-1.4l2.125 2.125l4.25-4.25l1.4 1.425zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
},
"person-off": {
"body": "<path fill=\"currentColor\" d=\"M19.775 22.625L17.15 20H4v-2.8q0-.85.438-1.562T5.6 14.55q1.125-.575 2.288-.925t2.362-.525L1.375 4.225L2.8 2.8l18.4 18.4zM18.4 14.55q.725.35 1.15 1.062T20 17.15l-3.35-3.35q.45.175.888.35t.862.4m-4.2-3.2L8.65 5.8q.575-.85 1.45-1.325T12 4q1.65 0 2.825 1.175T16 8q0 1.025-.475 1.9T14.2 11.35\"/>"
},
"person-remove": {
"body": "<path fill=\"currentColor\" d=\"M16 11V9h6v2zm-9.825-.175Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
},
"person-rounded": {
"body": "<path fill=\"currentColor\" d=\"M9.175 10.825Q8 9.65 8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12t-2.825-1.175M4 18v-.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2v.8q0 .825-.587 1.413T18 20H6q-.825 0-1.412-.587T4 18\"/>"
},
"photo-library-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13.25 12.5L12.1 11q-.15-.175-.4-.175t-.4.175l-1.675 2.2q-.2.25-.063.525t.463.275h7.95q.325 0 .462-.275t-.062-.525l-2.425-3.175q-.15-.2-.4-.2t-.4.2l-1.9 2.475ZM8 18q-.825 0-1.413-.588T6 16V4q0-.825.588-1.413T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.588 1.413T20 18H8Zm0-2h12V4H8v12Zm-4 6q-.825 0-1.413-.588T2 20V7q0-.425.288-.713T3 6q.425 0 .713.288T4 7v13h13q.425 0 .713.288T18 21q0 .425-.288.713T17 22H4ZM8 4v12V4Z\"/>",
"hidden": true
},
"picture-as-pdf-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10 10.5h1q.425 0 .713-.288T12 9.5v-1q0-.425-.288-.712T11 7.5H9.5q-.2 0-.35.15T9 8v4q0 .2.15.35t.35.15t.35-.15T10 12zm0-1v-1h1v1zm5 3q.425 0 .713-.288T16 11.5v-3q0-.425-.288-.712T15 7.5h-1.5q-.2 0-.35.15T13 8v4q0 .2.15.35t.35.15zm-1-1v-3h1v3zm4-1h.5q.2 0 .35-.15T19 10t-.15-.35t-.35-.15H18v-1h.5q.2 0 .35-.15T19 8t-.15-.35t-.35-.15h-1q-.2 0-.35.15T17 8v4q0 .2.15.35t.35.15t.35-.15T18 12zM8 18q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm-4 4q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6t.713.288T4 7v13h13q.425 0 .713.288T18 21t-.288.713T17 22z\"/>"
},
"pin-end": {
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v6h-2V6H4v12h10v2zm9.95-5.625L11 11.425v2.225H9V8h5.65v2H12.4l2.95 2.95zM19 20q-1.25 0-2.125-.875T16 17t.875-2.125T19 14t2.125.875T22 17t-.875 2.125T19 20\"/>"
},
"play-arrow-rounded": {
"body": "<path fill=\"currentColor\" d=\"M8 17.175V6.825q0-.425.3-.713t.7-.287q.125 0 .263.037t.262.113l8.15 5.175q.225.15.338.375t.112.475t-.112.475t-.338.375l-8.15 5.175q-.125.075-.262.113T9 18.175q-.4 0-.7-.288t-.3-.712\"/>"
},
"play-circle": {
"body": "<path fill=\"currentColor\" d=\"m9.5 16.5l7-4.5l-7-4.5zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"preview-off-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12 17q-2.05 0-3.662-1.112T6 13q.5-1.2 1.4-2.1t2.1-1.4l1.175 1.175q-.975.275-1.75.863T7.65 13q.65 1.175 1.8 1.838T12 15.5q.75 0 1.45-.2t1.275-.575L15.8 15.8q-.8.575-1.763.888T12 17m5.225-2.6l-1.075-1.075q.05-.075.1-.162t.1-.163q-.45-.825-1.175-1.412t-1.625-.863L11.825 9q2.05 0 3.75 1.113T18 13q-.15.375-.337.725t-.438.675M5 21q-.825 0-1.412-.587T3 19V5.825L2.075 4.9Q1.8 4.625 1.8 4.212t.3-.712q.275-.275.7-.275t.7.275l17 17q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-.9-.9zm0-2h11.175L5 7.825zm16-.825l-2-2V8h-8.175l-5-5H19q.825 0 1.413.588T21 5z\"/>"
},
"qr-code-rounded": {
"body": "<path fill=\"currentColor\" d=\"M3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11H4q-.425 0-.712-.288T3 10m2-1h4V5H5zM3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21H4q-.425 0-.712-.288T3 20m2-1h4v-4H5zm8-9V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11h-6q-.425 0-.712-.288T13 10m2-1h4V5h-4zm4 12v-2h2v2zm-6-6v-2h2v2zm2 2v-2h2v2zm-2 2v-2h2v2zm2 2v-2h2v2zm2-2v-2h2v2zm0-4v-2h2v2zm2 2v-2h2v2z\"/>"
},
"quick-reference-all-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 4v6.025V10v10zv5zm3 10h2q.425 0 .713-.288T11 13t-.288-.712T10 12H8q-.425 0-.712.288T7 13t.288.713T8 14m0 4h1q.425 0 .713-.288T10 17t-.288-.712T9 16H8q-.425 0-.712.288T7 17t.288.713T8 18m-3 4q-.825 0-1.412-.587T3 20V4q0-.825.588-1.412T5 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762v.7q0 .425-.288.7T18 10.5t-.712-.288T17 9.5V9h-4q-.425 0-.712-.288T12 8V4H5v16h5.5q.425 0 .713.288T11.5 21t-.288.713T10.5 22zm13.275-3.725Q19 17.55 19 16.5t-.725-1.775T16.5 14t-1.775.725T14 16.5t.725 1.775T16.5 19t1.775-.725m3.325 4.3q-.425 0-.7-.275l-2-2q-.525.35-1.137.525T16.5 21q-1.875 0-3.187-1.312T12 16.5t1.313-3.187T16.5 12t3.188 1.313T21 16.5q0 .65-.175 1.263T20.3 18.9l2 2q.275.275.275.7t-.275.7t-.7.275\"/>"
},
"radio-button-unchecked": {
"body": "<path fill=\"currentColor\" d=\"M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20\"/>"
},
"redo": {
"body": "<path fill=\"currentColor\" d=\"M9.9 19q-2.425 0-4.163-1.575T4 13.5t1.738-3.925T9.9 8h6.3l-2.6-2.6L15 4l5 5l-5 5l-1.4-1.4l2.6-2.6H9.9q-1.575 0-2.738 1T6 13.5T7.163 16T9.9 17H17v2z\"/>"
},
"refresh": {
"body": "<path fill=\"currentColor\" d=\"M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V4h2v7h-7V9h4.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.925 0 3.475-1.1T17.65 14h2.1q-.7 2.65-2.85 4.325T12 20\"/>"
},
"refresh-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20\"/>"
},
"remove-moderator-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M20 6.375V11.1q0 .875-.137 1.738t-.413 1.712q-.175.525-.55.688t-.725.037t-.587-.437t-.088-.763q.25-.7.375-1.462T18 11.1V6.375l-6-2.25l-3.05 1.15q-.275.1-.562.038T7.9 5.05q-.4-.4-.275-.925T8.25 3.4l3.05-1.125q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375M12 21.9q-.1 0-.625-.1Q8 20.675 6 17.637T4 11.1V6.8L2.1 4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l17 17q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-2.55-2.55q-.825.875-1.812 1.475t-2.113.975q-.15.05-.3.075T12 21.9m0-2q.875-.275 1.675-.775t1.475-1.175L6 8.8v2.3q0 3.025 1.7 5.5t4.3 3.3\"/>"
},
"remove-selection-rounded": {
"body": "<path fill=\"currentColor\" d=\"m16.075 17.475l-3.25 3.25q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l3.25-3.25l-3.25-3.25q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.25 3.25l3.25-3.25q.275-.275.7-.275t.7.275t.275.7t-.275.7l-3.25 3.25l3.25 3.25q.275.275.275.7t-.275.7t-.7.275t-.7-.275zM3.288 4.712Q3 4.426 3 4t.288-.712T4 3t.713.288T5 4t-.288.713T4 5t-.712-.288m4 0Q7 4.426 7 4t.288-.712T8 3t.713.288T9 4t-.288.713T8 5t-.712-.288m4 0Q11 4.426 11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5t-.712-.288m4 0Q15 4.426 15 4t.288-.712T16 3t.713.288T17 4t-.288.713T16 5t-.712-.288m4 0Q19 4.426 19 4t.288-.712T20 3t.713.288T21 4t-.288.713T20 5t-.712-.288m-12 16Q7 20.426 7 20t.288-.712T8 19t.713.288T9 20t-.288.713T8 21t-.712-.288m-4-12Q3 8.425 3 8t.288-.712T4 7t.713.288T5 8t-.288.713T4 9t-.712-.288m0 4Q3 12.426 3 12t.288-.712T4 11t.713.288T5 12t-.288.713T4 13t-.712-.288m0 4Q3 16.426 3 16t.288-.712T4 15t.713.288T5 16t-.288.713T4 17t-.712-.288m0 4Q3 20.426 3 20t.288-.712T4 19t.713.288T5 20t-.288.713T4 21t-.712-.288m16-12Q19 8.425 19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9t-.712-.288\"/>"
},
"rocket-launch": {
"body": "<path fill=\"currentColor\" d=\"m2.45 10.575l4.2-4.2q.35-.35.825-.5t.975-.05l1.3.275Q8.4 7.7 7.625 9t-1.5 3.15zm5.125 2.275q.575-1.8 1.563-3.4t2.387-3q2.2-2.2 5.025-3.287t5.275-.663q.425 2.45-.65 5.275T17.9 12.8q-1.375 1.375-3 2.388t-3.425 1.587zm8.313-2.425q.837 0 1.412-.575t.575-1.412t-.575-1.413t-1.412-.575t-1.413.575t-.575 1.413t.575 1.412t1.413.575m-2.113 11.45l-1.6-3.675q1.85-.725 3.163-1.5t2.912-2.125l.25 1.3q.1.5-.05.988t-.5.837zM4.05 16.05q.875-.875 2.125-.888t2.125.863t.875 2.125t-.875 2.125q-.625.625-2.088 1.075t-4.037.8q.35-2.575.8-4.025T4.05 16.05\"/>"
},
"rotate-left": {
"body": "<path fill=\"currentColor\" d=\"M11 22q-1.25-.125-2.4-.612T6.4 20.1l1.4-1.45q.725.525 1.538.85t1.662.45zm2 0v-2.05q2.6-.375 4.3-2.337T19 13.05q0-2.925-2.037-4.962T12 6.05h-.2l1.6 1.6l-1.4 1.4l-4-4l4-4l1.4 1.45l-1.55 1.55H12q1.875 0 3.513.713t2.85 1.925t1.925 2.85T21 13.05q0 3.425-2.275 5.963T13 22m-8.05-3.35q-.8-1.05-1.288-2.2t-.612-2.4H5.1q.125.85.45 1.663t.85 1.537zm-1.9-6.6q.15-1.275.625-2.45T4.95 7.45l1.45 1.4q-.525.725-.85 1.538T5.1 12.05z\"/>"
},
"rotate-right": {
"body": "<path fill=\"currentColor\" d=\"M13.05 22v-2.05q.85-.125 1.663-.45t1.537-.85l1.4 1.45q-1.05.8-2.2 1.287t-2.4.613m-2 0q-3.45-.45-5.725-2.988T3.05 13.05q0-1.875.713-3.512t1.925-2.85t2.85-1.925t3.512-.713h.15L10.65 2.5l1.4-1.45l4 4l-4 4l-1.4-1.4l1.6-1.6h-.2q-2.925 0-4.962 2.038T5.05 13.05q0 2.6 1.7 4.563t4.3 2.337zm8.05-3.35l-1.45-1.4q.525-.725.85-1.537t.45-1.663H21q-.125 1.25-.612 2.4t-1.288 2.2m1.9-6.6h-2.05q-.125-.85-.45-1.662t-.85-1.538l1.45-1.4q.8.975 1.275 2.15T21 12.05\"/>"
},
"rotate-right-rounded": {
"body": "<path fill=\"currentColor\" d=\"M19.75 12.05q-.3 0-.587-.213T18.8 11.3q-.125-.475-.312-.937t-.438-.888q-.175-.275-.125-.612T18.2 8.3q.375-.35.863-.3t.737.475q.325.55.575 1.125t.4 1.2q.125.5-.187.875t-.838.375m-6.7 8.7q0-.3.213-.587t.537-.363q.475-.125.938-.312t.887-.438q.275-.175.613-.125t.562.275q.35.375.3.863t-.475.737q-.575.325-1.15.563t-1.175.412q-.5.125-.875-.175t-.375-.85m5.15-2.95q-.225-.2-.275-.55t.125-.625q.25-.425.438-.888t.312-.937q.075-.325.35-.537t.6-.213q.55 0 .85.375t.175.875q-.175.6-.412 1.175t-.563 1.15q-.25.425-.737.475t-.863-.3m-8.375 3.95q-2.975-.8-4.875-3.2t-1.9-5.5q0-3.75 2.625-6.375T12.05 4.05h.15l-.875-.875q-.275-.275-.275-.7t.3-.725q.275-.275.7-.275t.725.275l2.575 2.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-2.6 2.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l.9-.9h-.2q-2.925 0-4.962 2.037T5.05 13.05q0 2.425 1.475 4.288T10.3 19.8q.325.075.538.35t.212.6q0 .525-.363.825t-.862.175\"/>"
},
"save": {
"body": "<path fill=\"currentColor\" d=\"M21 7v12q0 .825-.587 1.413T19 21H5q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h12zm-6.875 10.125Q15 16.25 15 15t-.875-2.125T12 12t-2.125.875T9 15t.875 2.125T12 18t2.125-.875M6 10h9V6H6z\"/>"
},
"save-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h11.175q.4 0 .763.15t.637.425l2.85 2.85q.275.275.425.638t.15.762V19q0 .825-.587 1.413T19 21zm9.125-3.875Q15 16.25 15 15t-.875-2.125T12 12t-2.125.875T9 15t.875 2.125T12 18t2.125-.875M7 10h7q.425 0 .713-.288T15 9V7q0-.425-.288-.712T14 6H7q-.425 0-.712.288T6 7v2q0 .425.288.713T7 10\"/>"
},
"scan-delete-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M6 4v5zv16v-.238V20zm0 18q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762V11.6q0 .425-.288.713T19 12.6t-.712-.287T18 11.6V9h-3q-.825 0-1.412-.587T13 7V4H6v16h6.2q.425 0 .713.288T13.2 21t-.287.713T12.2 22zm12-2.575L16.6 20.8q-.275.275-.687.288T15.2 20.8q-.275-.275-.275-.7t.275-.7l1.4-1.4l-1.4-1.4q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l1.4 1.4l1.4-1.4q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7L19.425 18l1.375 1.4q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275z\"/>"
},
"scan-delete-rounded": {
"body": "<path fill=\"currentColor\" d=\"M15 9h3l-5-5v3q0 .825.588 1.413T15 9m3 10.425L16.6 20.8q-.275.275-.687.288T15.2 20.8q-.275-.275-.275-.7t.275-.7l1.4-1.4l-1.4-1.4q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l1.4 1.4l1.4-1.4q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7L19.425 18l1.375 1.4q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275zM6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762v2.725q0 .3-.238.475t-.537.1q-1.475-.325-2.925.1t-2.55 1.525q-.8.8-1.275 1.887T12 17.976q0 .725.175 1.45T12.7 20.8q.225.425.025.813T12.1 22z\"/>"
},
"scanner-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M17.6 12L4.45 7.25q-.4-.15-.575-.525T3.85 5.95t.525-.575t.775-.025L19.8 10.7q.5.2.85.7t.35 1.1V18q0 .825-.587 1.413T19 20H5q-.825 0-1.412-.587T3 18v-4q0-.825.588-1.412T5 12zm1.4 6v-4H5v4zm-8-1h6q.425 0 .713-.288T18 16t-.288-.712T17 15h-6q-.425 0-.712.288T10 16t.288.713T11 17m-3.287-.288Q8 16.426 8 16t-.288-.712T7 15t-.712.288T6 16t.288.713T7 17t.713-.288M5 18v-4z\"/>"
},
"schedule-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13 11.6V8q0-.425-.288-.712T12 7t-.712.288T11 8v3.975q0 .2.075.388t.225.337l3.3 3.3q.275.275.7.275T16 16t.275-.7t-.275-.7zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.325 0 5.663-2.337T20 12t-2.337-5.663T12 4T6.337 6.338T4 12t2.338 5.663T12 20\"/>"
},
"search": {
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14\"/>"
},
"search-rounded": {
"body": "<path fill=\"currentColor\" d=\"M9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16m0-2q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14\"/>"
},
"security": {
"body": "<path fill=\"currentColor\" d=\"M12 22q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m0-2.1q2.425-.75 4.05-2.963T17.95 12H12V4.125l-6 2.25v5.175q0 .175.05.45H12z\"/>"
},
"select-all": {
"body": "<path fill=\"currentColor\" d=\"M7 17V7h10v10zm2-2h6V9H9zm-4 4v2q-.825 0-1.412-.587T3 19zm-2-2v-2h2v2zm0-4v-2h2v2zm0-4V7h2v2zm2-4H3q0-.825.588-1.412T5 3zm2 16v-2h2v2zM7 5V3h2v2zm4 16v-2h2v2zm0-16V3h2v2zm4 16v-2h2v2zm0-16V3h2v2zm4 16v-2h2q0 .825-.587 1.413T19 21m0-4v-2h2v2zm0-4v-2h2v2zm0-4V7h2v2zm0-4V3q.825 0 1.413.588T21 5z\"/>"
},
"send-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4.4 19.425q-.5.2-.95-.088T3 18.5V14l8-2l-8-2V5.5q0-.55.45-.837t.95-.088l15.4 6.5q.625.275.625.925t-.625.925z\"/>"
},
"settings-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10.825 22q-.675 0-1.162-.45t-.588-1.1L8.85 18.8q-.325-.125-.612-.3t-.563-.375l-1.55.65q-.625.275-1.25.05t-.975-.8l-1.175-2.05q-.35-.575-.2-1.225t.675-1.075l1.325-1Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337l-1.325-1Q2.675 9.9 2.525 9.25t.2-1.225L3.9 5.975q.35-.575.975-.8t1.25.05l1.55.65q.275-.2.575-.375t.6-.3l.225-1.65q.1-.65.588-1.1T10.825 2h2.35q.675 0 1.163.45t.587 1.1l.225 1.65q.325.125.613.3t.562.375l1.55-.65q.625-.275 1.25-.05t.975.8l1.175 2.05q.35.575.2 1.225t-.675 1.075l-1.325 1q.025.175.025.338v.674q0 .163-.05.338l1.325 1q.525.425.675 1.075t-.2 1.225l-1.2 2.05q-.35.575-.975.8t-1.25-.05l-1.5-.65q-.275.2-.575.375t-.6.3l-.225 1.65q-.1.65-.587 1.1t-1.163.45zm1.225-6.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5\"/>"
},
"shield-lock": {
"body": "<path fill=\"currentColor\" d=\"M12 22q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m-2-6h4q.425 0 .713-.288T15 15v-3q0-.425-.288-.712T14 11v-1q0-.825-.587-1.412T12 8t-1.412.588T10 10v1q-.425 0-.712.288T9 12v3q0 .425.288.713T10 16m1-5v-1q0-.425.288-.712T12 9t.713.288T13 10v1z\"/>"
},
"shield-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11.675 21.875q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025\"/>"
},
"show-chart": {
"body": "<path fill=\"currentColor\" d=\"M3.5 18.5L2 17l7.5-7.5l4 4l7.1-8L22 6.9l-8.5 9.6l-4-4z\"/>"
},
"signature-rounded": {
"body": "<path fill=\"currentColor\" d=\"M14.075 11.725q1.825-1.35 2.85-2.962T17.95 5.55q0-.8-.262-1.175T16.975 4Q15.8 4 14.9 5.988t-.9 4.487q0 .35.013.663t.062.587M4.1 16.3q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l.9-.9l-.9-.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l.9.9l.9-.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-.9.9l.9.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-.9-.9zm11.35-.3q-.75 0-1.375-.288T13 14.776q-.4.2-.825.4l-.85.4q-.4.175-.787.013T10 15.025t.038-.775t.587-.55l.825-.387q.4-.188.775-.388q-.125-.55-.187-1.2t-.063-1.4q0-3.6 1.425-5.962T16.975 2q1.3 0 2.125.963t.825 2.687q0 2.15-1.362 4.25t-3.788 3.775q.175.175.363.263t.387.087q.525 0 1.225-.575t1.35-1.55q.25-.35.638-.488t.762.038q.375.2.55.588t.1.812q-.05.3-.05.575t.075.525q.125-.05.288-.162t.337-.288q.3-.275.713-.312t.737.212q.35.275.375.675t-.25.675q-.575.575-1.213.913T19.95 16q-.525 0-.937-.312t-.688-.963q-.7.625-1.425.95T15.45 16M3.288 20.713Q3 20.425 3 20t.288-.712T4 19t.713.288T5 20t-.288.713T4 21t-.712-.288m4 0Q7 20.426 7 20t.288-.712T8 19t.713.288T9 20t-.288.713T8 21t-.712-.288m4 0Q11 20.426 11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21t-.712-.288m4 0Q15 20.426 15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21t-.712-.288m4 0Q19 20.426 19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21t-.712-.288\"/>"
},
"speed": {
"body": "<path fill=\"currentColor\" d=\"M10.45 15.5q.625.625 1.575.588T13.4 15.4L19 7l-8.4 5.6q-.65.45-.712 1.362t.562 1.538M5.1 20q-.55 0-1.012-.238t-.738-.712q-.65-1.175-1-2.437T2 14q0-2.075.788-3.9t2.137-3.175T8.1 4.788T12 4q2.05 0 3.85.775T19 6.888t2.15 3.125t.825 3.837q.025 1.375-.312 2.688t-1.038 2.512q-.275.475-.737.713T18.874 20z\"/>"
},
"star": {
"body": "<path fill=\"currentColor\" d=\"m5.825 21l1.625-7.025L2 9.25l7.2-.625L12 2l2.8 6.625l7.2.625l-5.45 4.725L18.175 21L12 17.275z\"/>"
},
"star-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 17.275l-4.15 2.5q-.275.175-.575.15t-.525-.2t-.35-.437t-.05-.588l1.1-4.725L3.775 10.8q-.25-.225-.312-.513t.037-.562t.3-.45t.55-.225l4.85-.425l1.875-4.45q.125-.3.388-.45t.537-.15t.537.15t.388.45l1.875 4.45l4.85.425q.35.05.55.225t.3.45t.038.563t-.313.512l-3.675 3.175l1.1 4.725q.075.325-.05.588t-.35.437t-.525.2t-.575-.15z\"/>"
},
"sticky-note-2": {
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v10l-6 6zm9-2l5-5h-5zm-7-5h5v-2H7zm0-4h10V8H7z\"/>"
},
"storage-rounded": {
"body": "<path fill=\"currentColor\" d=\"M5 20q-.825 0-1.412-.587T3 18t.588-1.412T5 16h14q.825 0 1.413.588T21 18t-.587 1.413T19 20zM5 8q-.825 0-1.412-.587T3 6t.588-1.412T5 4h14q.825 0 1.413.588T21 6t-.587 1.413T19 8zm0 6q-.825 0-1.412-.587T3 12t.588-1.412T5 10h14q.825 0 1.413.588T21 12t-.587 1.413T19 14zm1.713-7.288Q7 6.426 7 6t-.288-.712T6 5t-.712.288T5 6t.288.713T6 7t.713-.288m0 6Q7 12.426 7 12t-.288-.712T6 11t-.712.288T5 12t.288.713T6 13t.713-.288m0 6Q7 18.426 7 18t-.288-.712T6 17t-.712.288T5 18t.288.713T6 19t.713-.288\"/>"
},
"strikethrough-s": {
"body": "<path fill=\"currentColor\" d=\"M12.15 20q-1.9 0-3.375-1.125T6.65 15.8l2.2-.95q.35 1.2 1.213 1.975t2.137.775q1.05 0 1.9-.5t.85-1.6q0-.45-.175-.825T14.3 14h2.8q.125.35.188.713t.062.787q0 2.15-1.537 3.325T12.15 20M2 12v-2h20v2zm10.05-8.15q1.65 0 2.888.812T16.85 7.15l-2.2.975q-.225-.725-.837-1.3T12.1 6.25q-1.025 0-1.7.463T9.65 8h-2.4q.05-1.725 1.363-2.937T12.05 3.85\"/>"
},
"subdirectory-arrow-right-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13 19.575q0-.4.3-.7L16.175 16H7q-.825 0-1.412-.587T5 14V5q0-.425.288-.712T6 4t.713.288T7 5v9h9.175l-2.9-2.9q-.3-.3-.288-.7t.288-.7q.3-.3.7-.312t.7.287L19.3 14.3q.15.15.212.325t.063.375t-.063.375t-.212.325l-4.575 4.575q-.3.3-.712.3t-.713-.3t-.3-.7\"/>"
},
"swap-horiz": {
"body": "<path fill=\"currentColor\" d=\"m7 20l-5-5l5-5l1.4 1.425L5.825 14H13v2H5.825L8.4 18.575zm10-6l-1.4-1.425L18.175 10H11V8h7.175L15.6 5.425L17 4l5 5z\"/>"
},
"sync-alt-rounded": {
"body": "<path fill=\"currentColor\" d=\"m5.825 17l1.9 1.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-3.6-3.6q-.15-.15-.213-.325T2.426 16t.063-.375t.212-.325l3.6-3.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L5.825 15H20q.425 0 .713.288T21 16t-.288.713T20 17zm12.35-8H4q-.425 0-.712-.288T3 8t.288-.712T4 7h14.175l-1.9-1.9q-.3-.3-.287-.7t.312-.7q.3-.275.7-.288t.7.288l3.6 3.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-3.6 3.6q-.275.275-.687.275T16.3 12.3q-.3-.3-.3-.712t.3-.713z\"/>"
},
"system-update-alt-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h4q.425 0 .713.288T9 5t-.288.713T8 6H4v12h16V6h-4q-.425 0-.712-.288T15 5t.288-.712T16 4h4q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm7-8.4V5q0-.425.288-.712T12 4t.713.288T13 5v6.6l1.9-1.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-3.6 3.6q-.3.3-.7.3t-.7-.3l-3.6-3.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275z\"/>"
},
"text-fields": {
"body": "<path fill=\"currentColor\" d=\"M7 20V7H2V4h13v3h-5v13zm9 0v-8h-3V9h9v3h-3v8z\"/>"
},
"text-fields-rounded": {
"body": "<path fill=\"currentColor\" d=\"M7.438 19.563Q7 19.125 7 18.5V7H3.5q-.625 0-1.062-.437T2 5.5t.438-1.062T3.5 4h10q.625 0 1.063.438T15 5.5t-.437 1.063T13.5 7H10v11.5q0 .625-.437 1.063T8.5 20t-1.062-.437m9 0Q16 19.125 16 18.5V12h-1.5q-.625 0-1.062-.437T13 10.5t.438-1.062T14.5 9h6q.625 0 1.063.438T22 10.5t-.437 1.063T20.5 12H19v6.5q0 .625-.437 1.063T17.5 20t-1.062-.437\"/>"
},
"thread-unread-rounded": {
"body": "<path fill=\"currentColor\" d=\"M17.875 6.125Q17 5.25 17 4t.875-2.125T20 1t2.125.875T23 4t-.875 2.125T20 7t-2.125-.875M13.85 21q-1.35 0-2.275-.925t-.925-2.225q0-1.9 1.538-3.438t3.837-1.912q-.075-.9-.45-1.362t-1.025-.463q-.75 0-1.625.625t-2.075 2.05q-1.95 2.325-2.862 3.025t-1.963.7q-1.275 0-2.15-.95T3 13.825t.588-2.762t1.987-3.388q.475-.65.7-1.1T6.5 5.85q0-.175-.062-.262T6.25 5.5t-.3.075t-.375.275q-.375.35-.862.375T3.875 5.9q-.375-.4-.387-.937t.387-.888q.6-.525 1.2-.8T6.25 3q1.15 0 1.95.8t.8 2q0 .725-.375 1.6t-1.25 2.1q-.95 1.35-1.412 2.375t-.463 1.8q0 .425.138.663t.387.237t.438-.137t.687-.663q.325-.35.775-.862t1.1-1.263q1.575-1.875 2.85-2.675t2.675-.8q1.675 0 2.75 1.125t1.225 3.075h1.225q.525 0 .888.362t.362.888t-.363.888t-.887.362h-1.225q-.2 2.8-1.463 4.463T13.85 21m.05-2.5q.8 0 1.35-.913T16 15.05q-1.15.275-2 1.088t-.85 1.612q0 .35.2.55t.55.2\"/>"
},
"toc-rounded": {
"body": "<path fill=\"currentColor\" d=\"M4 17q-.425 0-.712-.288T3 16t.288-.712T4 15h12q.425 0 .713.288T17 16t-.288.713T16 17zm0-4q-.425 0-.712-.288T3 12t.288-.712T4 11h12q.425 0 .713.288T17 12t-.288.713T16 13zm0-4q-.425 0-.712-.288T3 8t.288-.712T4 7h12q.425 0 .713.288T17 8t-.288.713T16 9zm16 8q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m0-4q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0-4q-.425 0-.712-.288T19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9\"/>"
},
"touch-app-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10.475 22q-.7 0-1.312-.3t-1.038-.85L3.1 14.475q-.2-.225-.175-.537t.225-.513q.5-.525 1.2-.625t1.3.275L7.5 14.2V6q0-.425.288-.712T8.5 5t.725.288t.3.712v5H17q1.25 0 2.125.875T20 14v4q0 1.65-1.175 2.825T16 22zm1.5-13q-.425 0-.712-.288T10.975 8q0-.05.125-.5q.2-.35.3-.712T11.5 6q0-1.25-.875-2.125T8.5 3t-2.125.875T5.5 6q0 .425.1.788t.3.712q.075.125.1.25t.025.25q0 .425-.275.713T5.05 9q-.275 0-.512-.15t-.363-.375q-.325-.55-.5-1.175T3.5 6q0-2.075 1.463-3.537T8.5 1t3.538 1.463T13.5 6q0 .675-.175 1.3t-.5 1.175q-.125.225-.35.375t-.5.15\"/>"
},
"tune-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11.288 20.713Q11 20.425 11 20v-4q0-.425.288-.712T12 15t.713.288T13 16v1h7q.425 0 .713.288T21 18t-.288.713T20 19h-7v1q0 .425-.288.713T12 21t-.712-.288M4 19q-.425 0-.712-.288T3 18t.288-.712T4 17h4q.425 0 .713.288T9 18t-.288.713T8 19zm3.288-4.288Q7 14.425 7 14v-1H4q-.425 0-.712-.288T3 12t.288-.712T4 11h3v-1q0-.425.288-.712T8 9t.713.288T9 10v4q0 .425-.288.713T8 15t-.712-.288M12 13q-.425 0-.712-.288T11 12t.288-.712T12 11h8q.425 0 .713.288T21 12t-.288.713T20 13zm3.288-4.288Q15 8.425 15 8V4q0-.425.288-.712T16 3t.713.288T17 4v1h3q.425 0 .713.288T21 6t-.288.713T20 7h-3v1q0 .425-.288.713T16 9t-.712-.288M4 7q-.425 0-.712-.288T3 6t.288-.712T4 5h8q.425 0 .713.288T13 6t-.288.713T12 7z\"/>"
},
"undo": {
"body": "<path fill=\"currentColor\" d=\"M7 19v-2h7.1q1.575 0 2.738-1T18 13.5T16.838 11T14.1 10H7.8l2.6 2.6L9 14L4 9l5-5l1.4 1.4L7.8 8h6.3q2.425 0 4.163 1.575T20 13.5t-1.737 3.925T14.1 19z\"/>"
},
"unfold-less": {
"body": "<path fill=\"currentColor\" d=\"m8.9 20l-1.4-1.4l4.5-4.5l4.5 4.5l-1.4 1.4l-3.1-3.1zM12 9.9L7.5 5.4L8.9 4L12 7.1L15.1 4l1.4 1.4z\"/>"
},
"unfold-more": {
"body": "<path fill=\"currentColor\" d=\"m12 21l-4.5-4.5l1.45-1.45L12 18.1l3.05-3.05l1.45 1.45zM8.95 9.05L7.5 7.6L12 3.1l4.5 4.5l-1.45 1.45L12 6z\"/>"
},
"upload-file-rounded": {
"body": "<path fill=\"currentColor\" d=\"M11 14.825V18q0 .425.288.713T12 19t.713-.288T13 18v-3.175l.9.9q.15.15.338.225t.375.063t.362-.088t.325-.225q.275-.3.288-.7t-.288-.7l-2.6-2.6q-.15-.15-.325-.212T12 11.425t-.375.063t-.325.212l-2.6 2.6q-.3.3-.287.7t.312.7q.3.275.7.288t.7-.288zM6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762V20q0 .825-.587 1.413T18 22zm7-14q0 .425.288.713T14 9h4l-5-5z\"/>"
},
"upload-rounded": {
"body": "<path fill=\"currentColor\" d=\"M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20zm5-12.15L9.125 9.725q-.3.3-.712.288T7.7 9.7q-.275-.3-.288-.7t.288-.7l3.6-3.6q.15-.15.325-.212T12 4.425t.375.063t.325.212l3.6 3.6q.3.3.288.7t-.288.7q-.3.3-.712.313t-.713-.288L13 7.85V15q0 .425-.288.713T12 16t-.712-.288T11 15z\"/>"
},
"verified-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M10.95 12.7L9.5 11.275Q9.225 11 8.813 11t-.713.3q-.275.275-.275.7t.275.7l2.15 2.15q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.287-.7t-.287-.7q-.3-.3-.712-.312t-.713.287zm-2.8 9.05L6.7 19.3l-2.75-.6q-.375-.075-.6-.387t-.175-.688L3.45 14.8l-1.875-2.15q-.25-.275-.25-.65t.25-.65L3.45 9.2l-.275-2.825q-.05-.375.175-.688t.6-.387l2.75-.6l1.45-2.45q.2-.325.55-.438t.7.038l2.6 1.1l2.6-1.1q.35-.15.7-.038t.55.438L17.3 4.7l2.75.6q.375.075.6.388t.175.687L20.55 9.2l1.875 2.15q.25.275.25.65t-.25.65L20.55 14.8l.275 2.825q.05.375-.175.688t-.6.387l-2.75.6l-1.45 2.45q-.2.325-.55.438t-.7-.038l-2.6-1.1l-2.6 1.1q-.35.15-.7.038t-.55-.438m1.3-1.8l2.55-1.1l2.6 1.1l1.4-2.4l2.75-.65l-.25-2.8l1.85-2.1l-1.85-2.15l.25-2.8l-2.75-.6l-1.45-2.4L12 5.15l-2.6-1.1L8 6.45l-2.75.6l.25 2.8L3.65 12l1.85 2.1l-.25 2.85l2.75.6zM12 12\"/>"
},
"verified-user-outline": {
"body": "<path fill=\"currentColor\" d=\"M10.95 15.55L16.6 9.9l-1.425-1.425L10.95 12.7l-2.1-2.1l-1.425 1.425zM12 22q-3.475-.875-5.737-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m0-2.1q2.6-.825 4.3-3.3t1.7-5.5V6.375l-6-2.25l-6 2.25V11.1q0 3.025 1.7 5.5t4.3 3.3m0-7.9\"/>"
},
"verified-user-rounded": {
"body": "<path fill=\"currentColor\" d=\"m10.95 12.7l-1.4-1.4q-.3-.3-.7-.3t-.7.3t-.3.713t.3.712l2.1 2.125q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.3-.712t-.3-.713t-.712-.3t-.713.3zm.725 9.175q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025\"/>"
},
"view-carousel-rounded": {
"body": "<path fill=\"currentColor\" d=\"M3 17q-.425 0-.712-.288T2 16V8q0-.425.288-.712T3 7h2q.425 0 .713.288T6 8v8q0 .425-.288.713T5 17H3Zm5 2q-.425 0-.712-.288T7 18V6q0-.425.288-.712T8 5h8q.425 0 .713.288T17 6v12q0 .425-.288.713T16 19H8Zm11-2q-.425 0-.712-.288T18 16V8q0-.425.288-.712T19 7h2q.425 0 .713.288T22 8v8q0 .425-.288.713T21 17h-2Z\"/>",
"hidden": true
},
"view-list": {
"body": "<path fill=\"currentColor\" d=\"M9 20h11q.825 0 1.413-.587T22 18v-2H9zM2 8h5V4H4q-.825 0-1.412.588T2 6zm0 6h5v-4H2zm2 6h3v-4H2v2q0 .825.588 1.413T4 20m5-6h13v-4H9zm0-6h13V6q0-.825-.587-1.412T20 4H9z\"/>"
},
"visibility": {
"body": "<path fill=\"currentColor\" d=\"M15.188 14.688Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16t3.188-1.312m-5.1-1.276Q9.3 12.625 9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2t-1.912-.787m-4.738 3.55Q2.35 14.925 1 11.5q1.35-3.425 4.35-5.462T12 4t6.65 2.038T23 11.5q-1.35 3.425-4.35 5.463T12 19t-6.65-2.037\"/>"
},
"visibility-off-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M15.175 8.325q.725.725 1.063 1.65t.237 1.9q0 .375-.275.638t-.65.262t-.638-.262t-.262-.638q.125-.65-.075-1.25T13.95 9.6t-1.025-.65t-1.275-.1q-.375 0-.638-.275t-.262-.65t.263-.637t.637-.263q.95-.1 1.875.238t1.65 1.062M12 6q-.475 0-.925.037t-.9.138q-.425.075-.763-.125t-.462-.6t.088-.775t.612-.45q.575-.125 1.163-.175T12 4q3.425 0 6.263 1.8t4.337 4.85q.1.2.15.413t.05.437t-.038.438t-.137.412q-.45 1-1.112 1.875t-1.463 1.6q-.3.275-.7.225t-.65-.4t-.212-.763t.337-.687q.6-.575 1.1-1.25t.875-1.45q-1.25-2.525-3.613-4.012T12 6m0 13q-3.35 0-6.125-1.812T1.5 12.425q-.125-.2-.187-.437T1.25 11.5t.05-.475t.175-.45q.5-1 1.163-1.912T4.15 7L2.075 4.9q-.275-.3-.262-.712T2.1 3.5t.7-.275t.7.275l17 17q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275l-3.5-3.45q-.875.275-1.775.413T12 19M5.55 8.4q-.725.65-1.325 1.425T3.2 11.5q1.25 2.525 3.613 4.013T12 17q.5 0 .975-.062t.975-.138l-.9-.95q-.275.075-.525.113T12 16q-1.875 0-3.188-1.312T7.5 11.5q0-.275.038-.525t.112-.525zm4.2 4.2\"/>"
},
"visibility-rounded": {
"body": "<path fill=\"currentColor\" d=\"M15.188 14.688Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16t3.188-1.312m-5.1-1.276Q9.3 12.625 9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2t-1.912-.787m-4.2 3.787q-2.763-1.8-4.363-4.75q-.125-.225-.187-.462t-.063-.488t.063-.488t.187-.462q1.6-2.95 4.363-4.75T12 4t6.113 1.8t4.362 4.75q.125.225.188.463t.062.487t-.062.488t-.188.462q-1.6 2.95-4.362 4.75T12 19t-6.113-1.8\"/>"
},
"vital-signs-rounded": {
"body": "<path fill=\"currentColor\" d=\"M8.15 19.725q-.375-.275-.55-.7L5.3 13H2q-.425 0-.712-.288T1 12t.288-.712T2 11h4q.325 0 .563.175t.362.475L9 17.1l4.6-12.125q.175-.425.55-.7T15 4t.85.275t.55.7L18.7 11H22q.425 0 .713.288T23 12t-.288.713T22 13h-4q-.325 0-.562-.175t-.363-.475L15 6.9l-4.6 12.125q-.175.425-.55.7T9 20t-.85-.275\"/>"
},
"warning": {
"body": "<path fill=\"currentColor\" d=\"M1 21L12 2l11 19zm11.713-3.287Q13 17.425 13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18t.713-.288M11 15h2v-5h-2z\"/>"
},
"warning-rounded": {
"body": "<path fill=\"currentColor\" d=\"M2.725 21q-.275 0-.5-.137t-.35-.363t-.137-.488t.137-.512l9.25-16q.15-.25.388-.375T12 3t.488.125t.387.375l9.25 16q.15.25.138.513t-.138.487t-.35.363t-.5.137zm9.988-3.287Q13 17.425 13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18t.713-.288m0-3Q13 14.425 13 14v-3q0-.425-.288-.712T12 10t-.712.288T11 11v3q0 .425.288.713T12 15t.713-.288\"/>"
},
"workspace-premium-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 11.925l-1.7 1.3q-.15.125-.3.013t-.1-.288l.65-2.1l-1.75-1.4q-.125-.125-.075-.287T8.95 9h2.15l.65-2.05q.05-.175.25-.175t.25.175L12.9 9h2.125q.175 0 .238.163t-.063.287l-1.775 1.4l.65 2.1q.05.175-.1.288t-.3-.013zM12 21l-4.675 1.55q-.5.175-.913-.125t-.412-.8v-6.35q-.95-1.05-1.475-2.4T4 10q0-3.35 2.325-5.675T12 2t5.675 2.325T20 10q0 1.525-.525 2.875T18 15.275v6.35q0 .5-.413.8t-.912.125zm4.25-6.75Q18 12.5 18 10t-1.75-4.25T12 4T7.75 5.75T6 10t1.75 4.25T12 16t4.25-1.75M8 20.025L12 19l4 1.025v-3.1q-.875.5-1.888.788T12 18t-2.113-.288T8 16.926zm4-1.55\"/>"
},
"workspace-premium-rounded": {
"body": "<path fill=\"currentColor\" d=\"m12 11.925l-1.7 1.3q-.15.125-.3.013t-.1-.288l.65-2.1l-1.75-1.4q-.125-.125-.075-.287T8.95 9h2.15l.65-2.05q.05-.175.25-.175t.25.175L12.9 9h2.125q.175 0 .238.163t-.063.287l-1.775 1.4l.65 2.1q.05.175-.1.288t-.3-.013zM12 21l-4.675 1.55q-.5.175-.913-.125t-.412-.8v-6.35q-.95-1.05-1.475-2.4T4 10q0-3.35 2.325-5.675T12 2t5.675 2.325T20 10q0 1.525-.525 2.875T18 15.275v6.35q0 .5-.413.8t-.912.125zm4.25-6.75Q18 12.5 18 10t-1.75-4.25T12 4T7.75 5.75T6 10t1.75 4.25T12 16t4.25-1.75\"/>"
},
"zoom-in": {
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14m-1-1.5v-2h-2v-2h2v-2h2v2h2v2h-2v2z\"/>"
},
"zoom-in-map-rounded": {
"body": "<path fill=\"currentColor\" d=\"m7 18.4l-2.4 2.4q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7L5.6 17H4q-.425 0-.713-.287T3 16t.288-.712T4 15h4q.425 0 .713.288T9 16v4q0 .425-.288.713T8 21t-.712-.288T7 20zm10 0V20q0 .425-.288.713T16 21t-.712-.288T15 20v-4q0-.425.288-.712T16 15h4q.425 0 .713.288T21 16t-.288.713T20 17h-1.6l2.4 2.4q.275.275.275.7t-.275.7t-.7.275t-.7-.275zM5.6 7L3.2 4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275L7 5.6V4q0-.425.288-.713T8 3t.713.288T9 4v4q0 .425-.288.713T8 9H4q-.425 0-.712-.288T3 8t.288-.712T4 7zm12.8 0H20q.425 0 .713.288T21 8t-.288.713T20 9h-4q-.425 0-.712-.288T15 8V4q0-.425.288-.712T16 3t.713.288T17 4v1.6l2.4-2.4q.275-.275.7-.275t.7.275t.275.7t-.275.7z\"/>"
},
"zoom-out": {
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14M7 10.5v-2h5v2z\"/>"
}
},
"lastModified": 1773740798,
"aliases": {
"cloud-upload": {
"parent": "backup"
},
"crop-square": {
"parent": "square-rounded"
}
},
"width": 24,
"height": 24
}