Compare commits

...
Author SHA1 Message Date
EthanHealy01 df00d61480 feat(settings): remove custom accent-colour picker (presets + Default only)
Drop the custom-hue ColorInput from the accent picker in settings — users pick
from the preset swatches or Default, no arbitrary hues. Removes the ColorInput
usage and its now-dead deps (draft state, clampAccentChoice, DEFAULT_ACCENT_COLOR,
ColorInput import). The preset grid + Default cell (shared ColorGridPicker) and
everything else are unchanged.
2026-07-14 15:58:40 +01:00
EthanHealy01 562166419b chore(lint): remove now-dead manager.tsx import exemption
Follow-up to removing .storybook/manager.tsx — the one-file no-restricted-imports
exemption pointed at a file that no longer exists.
2026-07-14 13:49:29 +01:00
EthanHealy01 2b77fb345a chore(storybook): remove accent-picker manager addon + its lint exemption
The .storybook/manager.tsx accent toolbar was the only thing importing editor
code into Storybook's (alias-less, esbuild) manager bundle, forcing a lint
exemption. Remove the addon and the exemption entirely. preview.tsx still
declares accentLight/accentDark globals (default accent) so stories are
unaffected; the swatch picker toolbar is just gone.

eslint clean + storybook build passes, no import overrides remain.
2026-07-14 13:49:29 +01:00
EthanHealy01 bedf797f46 fix(storybook): manager.tsx imports relatively (esbuild manager has no aliases)
The prior change aliased manager.tsx to @core, but Storybook's manager bundle
(esbuild) doesn't resolve aliases — only the preview/Vite bundle does — so the
storybook build failed. Revert manager.tsx to a relative import and scope a
one-file no-restricted-imports exemption to .storybook/manager.tsx (the only
file that genuinely can't use an alias). preview.tsx keeps @public (Vite-built).

Verified: eslint clean + storybook build succeeds.
2026-07-14 13:35:20 +01:00
EthanHealy01andGitHub 86378dbc64 Merge branch 'UI/consolidate-theme-variables' into UI/color-token-migration 2026-07-14 12:18:07 +01:00
EthanHealy01 d560335a6c chore(lint): drop blanket no-restricted-imports:off for node scripts
Removing the nodeGlobs 'off' (it masked a real violation) and fixing the two
imports it was hiding, so the relative-import ban applies to build tooling too:
- .storybook/manager.tsx: relative editor/src import -> @core/constants/theme
- .storybook/preview.tsx: relative public-asset import -> new @public/* alias
  (wired in main.ts viteFinal + .storybook/tsconfig paths), since no src alias
  covers public/. No eslint-disable / suppressions.

Node globals for the nodeGlobs layer are kept.
2026-07-14 12:09:05 +01:00
EthanHealy01 73704feb10 chore(lint): drop blanket no-restricted-imports:off for node scripts
Removing the nodeGlobs 'off' (it masked a real violation) and fixing the two
imports it was hiding, so the relative-import ban applies to build tooling too:
- .storybook/manager.tsx: relative editor/src import -> @core/constants/theme
- .storybook/preview.tsx: relative public-asset import -> new @public/* alias
  (wired in main.ts viteFinal + .storybook/tsconfig paths), since no src alias
  covers public/. No eslint-disable / suppressions.

Node globals for the nodeGlobs layer are kept.
2026-07-14 12:08:31 +01:00
EthanHealy01andGitHub b49b04bd68 Merge branch 'main' into UI/consolidate-theme-variables 2026-07-14 11:37:39 +01:00
EthanHealy01 873caf5172 merge main 2026-07-14 11:34:49 +01:00
EthanHealy01 193b096495 fix(theme): restore --color-hero-navy for the white hero CTA text
The main merge dropped --color-hero-navy from tokens.css on the assumption
nothing referenced it, but the white hero CTA text (EditorStatusCard.css /
WelcomeBanner.css) still uses it — leaving it undefined (invisible text on the
white button). Restore the token; the hero *background* stays accent-responsive.
2026-07-14 01:10:24 +01:00
EthanHealy01 1cdecfaf58 Merge remote-tracking branch 'origin/main' into UI/consolidate-theme-variables 2026-07-14 00:02:26 +01:00
EthanHealy01 c6f8ed43fd refactor(ui): extract shared ColorGridPicker for the accent picker
Per review on #7009: move the accent swatch grid out of GeneralSection into a
reusable core/ui/ColorGridPicker (swatch trigger + popover + radiogroup grid
with an optional default/unset icon cell + a footer slot). GeneralSection now
renders <ColorGridPicker> and passes the presets, default option and the
custom-colour ColorInput as the footer.

Removes the three raw-<button> eslint-disables from GeneralSection; the raw
elements now live in the DS layer (core/ui/), which the lint rule exempts.
Markup/styles preserved verbatim, so the control is unchanged visually.
2026-07-13 19:09:26 +01:00
EthanHealy01 05de5d7e2d chore(i18n): drop em-dash from theme accent description
Per review on #7009 — avoid em-dashes in UI copy.
2026-07-13 18:58:53 +01:00
EthanHealy01 449353150d feat(lint): make contrast check blocking + fix dark subtle-text AA
Per review on #7009: the contrast check was warn-only and opt-in, so it would
never catch regressions. Enforce it here (the follow-up PR):
- theme-lint.mjs `contrast` mode now exits non-zero on any sub-floor pair
- wired into `task frontend:lint:colors` (runs in the blocking lint gate)
- fixed the two failing pairs: --c-text-subtle on dark/portal-dark surfaces
  (3.36/3.67 → 4.74/5.18) by adding --p-zinc-250 and pointing dark subtle at it

All text-on-surface / on-primary pairs now clear WCAG AA per theme.
2026-07-13 18:49:46 +01:00
EthanHealy01 0f6f5b4e0b feat(lint): enforce no hardcoded colour in TS/TSX DOM code (code-colors)
Add a blocking 'code-colors' mode to theme-lint: default-deny with layered
exemptions (structural; var()/readColor/canvas/pdf-lib contexts; a
// theme-allow-color opt-out; exempt paths for rendering/vendor/config/
illustration areas). File list from 'git ls-files' (no directory walk).
Wired into 'task frontend:lint:colors'; README + script header updated.

Also removes the temporary color-migration-audit.md scratch file.
2026-07-13 17:42:21 +01:00
EthanHealy01 941dc35cda refactor(ui): route TS/TSX inline-style colours through the palette
Migrate hardcoded colour literals in DOM inline styles to tokens:
- auth screens (OAuthConsent/LoggedInState) + Login/Signup -> fixed --p-*
  (AuthLayout forces light; semantic --c-* would break there)
- FileCard/LanguagePicker/ErrorBoundary/ToolStep/etc -> --c-* / --p-*
- Payg avatar palette + DesktopOnboardingModal gradient -> exact --p-*
- rgba tints -> color-mix(in srgb, var(--p-*) N%, transparent)

Canvas/PDF rendering, colour pickers, vendor brand and self-contained docs
keep numeric colour by design.
2026-07-13 17:40:43 +01:00
EthanHealy01 93bc796e2f add primatives and remove the rgb codes and hex codes that we can remove 2026-07-13 16:36:12 +01:00
EthanHealy01 544a1f02eb feat(lint): enforce zero hardcoded colour across all source CSS
Add a blocking 'css-colors' mode to theme-lint that flags any hardcoded
colour in source .css under editor/src (primitives.css + generated
output.css exempt). File list is sourced from 'git ls-files' rather than a
directory walk, so there's no readdir→readFile path; comments and structural
black/white/transparent are ignored. Wired into 'task frontend:lint:colors'.

Update theme README + script header for the expanded palette and the now
app-wide enforcement.
2026-07-13 15:08:03 +01:00
EthanHealy01 6800db8029 refactor(css): migrate all hardcoded colours to primitives (CSS → zero literals)
Move every non-structural colour literal in source CSS into the primitive
palette and reference via var(--p-*):
- 194 hex + 139 rgba() across 38 files replaced
- 90 new primitives added (brand azure/red, violet/indigo, cyan, pink,
  emerald, slate, navy, Notion paper, illustration art, vendor/OS-media)
- near-duplicate neutrals snapped to existing ramp values
- coloured rgba() alpha tints -> color-mix(in srgb, var(--p-*) N%, transparent)
- structural rgba(0,0,0/255,255,255) and comment breadcrumbs left intact

Source CSS now contains zero hardcoded colour outside primitives.css.
Build + theme-lint pass; every var(--p-*) reference resolves.
2026-07-13 15:03:44 +01:00
EthanHealy01 2748fe3157 refactor(css): consolidate redundant status/brand colours to tokens
- billing status chips (FULL/WARNED/DEGRADED) -> adaptive --c-success/
  warning/danger(-subtle); now theme-aware instead of fixed pale literals
- unify saas guest-button red #9c2f30 -> #af3434 (single Stirling red)
- unify SpendCapControl dark accent #7ab4ff -> #66b8ff (match billing azure)
- disabled plan-button grey #7e7e7e -> var(--p-gray-500)

Distinct non-structural CSS hex: 95 -> 86.
2026-07-13 14:43:02 +01:00
EthanHealy01 b7bcbc9f45 refactor(css): merge adjacent duplicate rulesets into grouped selectors
Merge only ADJACENT rules (consecutive, whitespace-only gap, same nesting) with
identical declaration blocks — cascade-safe (non-adjacent merges can reorder the
cascade, so left alone). 22 merges across 14 files, net -104 lines. Build +
theme-lint green.
2026-07-13 11:47:15 +01:00
EthanHealy01 d7f51d7f72 feat(theme): add --c-warning + subtle status tokens; migrate auth alerts
Adds --c-warning (amber, per-theme) and --c-success/danger/warning-subtle
(pale status surfaces) to colors.css — the recurring tokens the consolidation
agents flagged as missing. Migrates the auth error/success alert boxes onto
them. Remaining status-colour consumers tracked in color-migration-audit.md.
2026-07-13 10:58:56 +01:00
EthanHealy01 2c0728c1a7 refactor(theme): collapse dark-override blocks to adaptive --c-* (agents)
Six parallel passes across component CSS: collapsed manual light/dark override
blocks to the adaptive --c-* tokens (navy overrides → neutral surfaces, per the
neutral-dark direction), migrated semantic colours to --c-*, stripped dead
--c-*/--p-* fallbacks, removed a duplicate ruleset. Brand/data-viz/illustration
colours kept and catalogued in color-migration-audit.md with the --c-* tokens
still needed to finish. Build + theme-lint green.
2026-07-13 10:55:38 +01:00
EthanHealy01 cca8c700d3 refactor(theme): strip dead --c-primary hex fallbacks (partial)
Removes the dead #6366f1 fallback from var(--c-primary, #6366f1) in 4 files
(--c-primary is always defined). Partial progress on the colour consolidation;
the full override-collapse pass is pending (agent fan-out blocked).
2026-07-13 09:41:07 +01:00
EthanHealy01 17b0fae153 refactor(theme): tokenize exact-match hardcoded colours (script)
Auto-replaced 48 hardcoded hex colours that match a primitive within Δ≤2
(visually identical) with var(--p-*), across all component CSS. Generated
color-migration-audit.md listing the 50 near-matches and 93 genuinely-unique
colours for the follow-up agent consolidation pass. Build + theme-lint green.
2026-07-13 02:02:33 +01:00
EthanHealy01 c5fbf85035 refactor(theme): tokenize matching hex + remove dead legacy tokens
- theme.css: convert the 5 hardcoded UI colours that exactly match a primitive
  to var(--p-*) / color-mix(var(--p-*)); bespoke logo/illustration fills, brand
  azure/gradients and the deliberate flash-yellow have no primitive equivalent
  and are left.
- Remove 11 verified-dead legacy tokens (--text-always-*, --landing-drop-*,
  --link-*) — 0 refs and not built dynamically (excluded all --color-*/--accent-*
  since those prefixes ARE constructed at runtime). theme.css 902 → 865 lines.
Build + theme-lint green.
2026-07-13 01:50:36 +01:00
EthanHealy01 ee6684f6a4 refactor(theme): delete compat.css — components reference --c-* directly
Migrated all remaining legacy-alias consumers (incl. stories/tests) to --c-*
(199 refs / 58 files), promoted the computed aliases to first-class tokens
(--c-accent-fg made universal on :root; --c-primary-tint / --c-primary-border
added), then removed compat.css, its @import, and its linter entry. No legacy
colour-alias layer remains; build + theme-lint green.

The larger theme.css/tokens.css legacy definition files are a separate cleanup.
2026-07-13 01:25:38 +01:00
EthanHealy01 925caf6d11 refactor(theme): drop 71 now-dead compat aliases
After migrating consumers to --c-* (prior commit), 71 of the 93 1:1 compat
aliases have zero references anywhere (verified across src + tailwind.config +
index.html; the 22 that remain are kept alive by stories/portal mantine theme).
Removing the dead ones cuts compat.css roughly in half — the real consolidation
payoff, not just a rename.
2026-07-13 01:14:13 +01:00
EthanHealy01 a70f80279a chore: remove one-shot compat-colour codemod after applying it
The migration is permanent in the committed files; the throwaway walker isn't
referenced anywhere and tripped Aikido's readdir→readFile heuristic.
2026-07-13 01:10:39 +01:00
EthanHealy01 99be0ae3c6 fix(theme): loading splash follows theme (no white flash in dark mode)
LoadingFallback renders inside Suspense before MantineProvider sets its colour
scheme, so --mantine-color-body was still Mantine's light default (white flash
when the portal/app splash appears in dark mode). Switch to --c-bg/--c-text,
which are driven by the pre-paint attributes on <html> and are correct from the
first frame.
2026-07-13 00:56:51 +01:00
EthanHealy01andGitHub d8599e2c6f Merge branch 'main' into UI/consolidate-theme-variables 2026-07-13 00:51:20 +01:00
EthanHealy01 7b43bb483e refactor(theme): migrate hardcoded hex in component CSS to tokens
Per-area passes replaced hardcoded surface/text/border/status hex in component
stylesheets with semantic --c-* tokens (--p-* only where an exact value had to
be preserved). Left intentionally: structural black/white/transparent, OAuth
brand colours, deliberate multi-hue gradients, data-viz/status hues without a
semantic fit, and dead var(--c-*, #fallback) fallbacks. ~90 sites across 17
files. Build + theme-lint green.
2026-07-13 00:48:40 +01:00
EthanHealy01 5f6b88005e refactor(theme): migrate compat colour aliases to --c-* tokens
Codemod (editor/scripts/migrate-compat-colors.mjs) rewrites every var(--legacy)
reference to the canonical --c-* token it maps to in compat.css (1:1 aliases
only; computed color-mix/fallback aliases left in place). 218 files, 1947
references. Pure rename — compat.css already guarantees each alias equals its
--c-* target, so no behaviour change.
2026-07-13 00:36:08 +01:00
EthanHealy01 41f507a9f0 chore(theme): dedupe default-blue literal in theme constants
Collapse the two #3b82f6 occurrences (DEFAULT_ACCENT_COLOR + first preset) into
a single documented BLUE_500 const. The accent hexes stay JS literals by
necessity — stored verbatim and parsed by deriveAccessiblePrimary, so CSS
var(--p-*) refs can't be used; theme-lint scopes to CSS only.
2026-07-13 00:25:56 +01:00
EthanHealy01 ffc0755b37 chore(storybook): use DEFAULT_ACCENT_COLOR instead of hardcoded #3b82f6
Replace the literal blue in preview.tsx globalTypes/fallback and manager.tsx
with the DEFAULT_ACCENT_COLOR constant from constants/theme.
2026-07-13 00:23:30 +01:00
EthanHealy01 91e0815f93 fix(theme): lift dark canvas ~2% off pure black (#0a0a0b → #0f0f10)
Dark mode's darkest surface (--c-bg / --p-zinc-950) read as near-black. Lift
it ~2% lighter across every representation (primitive, Tailwind --gray-50/
--background channels, Mantine dark-7) so the canvas is a deep charcoal, never
pure black — consistent, not a one-off override.
2026-07-13 00:04:26 +01:00
EthanHealy01 e3b4bc2412 chore(theme-lint): read a fixed file list, not readdir→readFile
Static scanners (Aikido) flag the directory-listing→file-read flow as a
file-inclusion risk. Read a fixed THEME_FILES list via constant paths instead
(matching the other unflagged scripts); readdir is now only used to fail if a
new theme .css isn't registered, so coverage still can't lapse.
2026-07-12 23:59:31 +01:00
EthanHealy01 e9785b6b2f chore(theme): harden lint script, trim comments, fix CI lint/format
- theme-lint.mjs: resolve root, skip symlinks, refuse reads outside the
  theme dir (addresses Aikido file-inclusion finding)
- remove unused eslint-disable in .storybook/preview.tsx (frontend-validation
  --max-warnings=0)
- condense multi-line comments to one line; drop style-choice CSS comments
- prettier formatting
2026-07-12 23:54:26 +01:00
EthanHealy01 31b7c0069d fix comment 2026-07-12 23:18:33 +01:00
EthanHealy01 a8a5f3a88f initial colors and theme improvements 2026-07-12 23:17:37 +01:00
294 changed files with 5482 additions and 4249 deletions
+16
View File
@@ -203,6 +203,22 @@ tasks:
cmds:
- task: lint:eslint
- task: lint:dpdm
- task: lint:colors
lint:colors:
desc: "Enforce theme tokens — all CSS colours route through the palette"
deps: [install]
cmds:
- node editor/scripts/lint/theme-lint.mjs
- node editor/scripts/lint/theme-lint.mjs css-colors
- node editor/scripts/lint/theme-lint.mjs code-colors
- node editor/scripts/lint/theme-lint.mjs contrast
contrast:
desc: "Check theme token pairs clear WCAG AA contrast per theme"
deps: [install]
cmds:
- node editor/scripts/lint/theme-lint.mjs contrast
lint:eslint:
desc: "Run ESLint linting"
+2
View File
@@ -155,6 +155,8 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
For a broader explanation of the frontend layering and override architecture, read @frontend/editor/DeveloperGuide.md
Before touching colours or theming (tokens, dark mode, accent colours), read @frontend/editor/src/core/theme/README.md — it explains the palette/`--c-*` token system and the rule that literal colours live only in `primitives.css`.
```typescript
// ✅ CORRECT - Use @app/* for all imports
import { AppLayout } from "@app/components/AppLayout";
+4
View File
@@ -38,6 +38,10 @@ const config: StorybookConfig = {
// modules (e.g. the auth supabase client that moved into proprietary).
"@proprietary": resolve(__dirname, "../editor/src/proprietary"),
"@core": resolve(__dirname, "../editor/src/core"),
// Public assets (e.g. the en-US translation TOML loaded ?raw by preview.tsx).
// No src alias covers public/, so this lets the config use an alias rather
// than a relative path.
"@public": resolve(__dirname, "../editor/public"),
};
// Editor stories import via @app/* (proprietary→core fallback), @core/* and
// @proprietary/*. Resolve them exactly the way the editor's own build does —
+41 -2
View File
@@ -17,6 +17,8 @@ import { LinkProvider, type LinkState } from "@portal/contexts/LinkContext";
import { ThemeProvider, useTheme } from "@portal/contexts/ThemeContext";
import { UIProvider } from "@portal/contexts/UIContext";
import { SuiProvider } from "@portal/theme/SuiProvider";
import { deriveAccessiblePrimary } from "@core/utils/customPrimary";
import { DEFAULT_ACCENT_COLOR } from "@core/constants/theme";
import { handlers } from "@portal/mocks/handlers";
import { configureSupabase } from "@proprietary/auth/supabase/supabaseClient";
import i18next from "i18next";
@@ -24,11 +26,11 @@ import { initReactI18next } from "react-i18next";
import { parse as parseToml } from "smol-toml";
// Load the real English copy so stories render human text, not raw keys. Bundled
// synchronously via ?raw so it's present on the very first render (no async flash).
// eslint-disable-next-line no-restricted-imports -- Storybook-only: read the public i18n asset; no @-alias covers editor/public/.
import enTranslationToml from "../editor/public/locales/en-US/translation.toml?raw";
import enTranslationToml from "@public/locales/en-US/translation.toml?raw";
import "@mantine/core/styles.css";
import "@core/tokens/tokens.css";
import "@core/theme/index.css";
import "@core/tokens/base.css";
// Storybook-only: init react-i18next with the real English resources parsed from
@@ -124,6 +126,33 @@ function ThemeBridge({
return <>{children}</>;
}
/**
* Tints the canvas from the toolbar accent picker (see .storybook/manager.tsx),
* exactly like the editor: always `data-app-theme="custom"`, with
* --user-primary/-on/-accent-fg derived from the active mode's accent global.
*/
function AccentInjector({
scheme,
accent,
}: {
scheme: "light" | "dark";
accent: string;
}) {
useEffect(() => {
const root = document.documentElement;
root.setAttribute("data-app-theme", "custom");
root.setAttribute("data-mantine-color-scheme", scheme);
const { primary, onPrimary, accentForeground } = deriveAccessiblePrimary(
accent,
scheme,
);
root.style.setProperty("--user-primary", primary);
root.style.setProperty("--user-primary-on", onPrimary);
root.style.setProperty("--user-accent-fg", accentForeground);
}, [scheme, accent]);
return null;
}
const withProviders: Decorator = (Story, context) => {
const tier = (context.globals.tier as Tier) ?? "pro";
const linkState =
@@ -135,9 +164,15 @@ const withProviders: Decorator = (Story, context) => {
// anything that isn't "dark" as light — matching the addon's own
// `selected || defaultTheme` fallback where defaultTheme is light.
const colorScheme = context.globals.theme === "dark" ? "dark" : "light";
// Each mode has its own accent (set by the toolbar accent picker).
const accent =
(colorScheme === "dark"
? (context.globals.accentDark as string)
: (context.globals.accentLight as string)) ?? DEFAULT_ACCENT_COLOR;
return (
<MemoryRouter initialEntries={["/"]}>
<ThemeProvider>
<AccentInjector scheme={colorScheme} accent={accent} />
<ThemeBridge theme={colorScheme}>
<SuiProvider colorScheme={colorScheme}>
{/* LinkProvider must wrap TierProvider: TierContext derives its tier
@@ -182,6 +217,10 @@ const preview: Preview = {
},
},
globalTypes: {
// Per-mode accent colours, driven by the custom toolbar picker in
// .storybook/manager.tsx (no built-in toolbar UI of their own).
accentLight: { name: "Accent (light)", defaultValue: DEFAULT_ACCENT_COLOR },
accentDark: { name: "Accent (dark)", defaultValue: DEFAULT_ACCENT_COLOR },
tier: {
name: "Tier",
description: "Subscription tier — drives useTier() everywhere",
+2 -1
View File
@@ -12,7 +12,8 @@
],
"@core/*": ["../editor/src/core/*"],
"@proprietary/*": ["../editor/src/proprietary/*"],
"@portal/*": ["../editor/src/portal/*"]
"@portal/*": ["../editor/src/portal/*"],
"@public/*": ["../editor/public/*"]
},
"resolveJsonModule": true,
"esModuleInterop": true,
+45
View File
@@ -14,6 +14,51 @@
<link rel="manifest" href="manifest.json" />
<title>Stirling PDF</title>
<!-- Apply theme + accent before first paint (no FOUC); mirrors ThemeProvider, which then refines it. -->
<script>
(function () {
try {
var p = JSON.parse(
localStorage.getItem("stirlingpdf_preferences") || "{}",
);
var mode = p.theme || "system";
var scheme =
mode === "light"
? "light"
: mode === "dark"
? "dark"
: window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
var stored = scheme === "dark" ? p.darkPrimary : p.lightPrimary;
// "default" (or unset) = neutral surfaces + blue buttons; a hex tints.
var isDefault = !stored || stored === "default";
var accent = isDefault ? "#3b82f6" : stored;
var r = document.documentElement;
r.setAttribute("data-theme", scheme);
r.setAttribute("data-app-theme", "custom");
r.setAttribute("data-accent", isDefault ? "default" : "custom");
r.setAttribute("data-mantine-color-scheme", scheme);
r.style.setProperty("--user-primary", accent);
r.style.setProperty("--user-accent-fg", accent);
var h = accent.replace("#", "");
if (h.length === 3) h = h.replace(/./g, "$&$&");
var lum =
(0.2126 * parseInt(h.slice(0, 2), 16) +
0.7152 * parseInt(h.slice(2, 4), 16) +
0.0722 * parseInt(h.slice(4, 6), 16)) /
255;
r.style.setProperty(
"--user-primary-on",
lum > 0.7 ? "#000000" : "#ffffff",
);
} catch (e) {
/* first visit / unavailable storage: ThemeProvider handles it */
}
})();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -9007,9 +9007,18 @@ languageDescription = "Choose the display language"
logout = "Log out"
setAsDefault = "Set as Default"
theme = "Theme"
themeAccent = "Accent colour"
themeAccentCustom = "Custom colour"
themeAccentCustomHint = "Custom (defaults recommended)"
themeAccentDark = "Dark mode accent colour"
themeAccentDefault = "Default"
themeAccentDefaultHint = "Default theme (recommended)"
themeAccentDescription = "Buttons, links and highlights follow it, and it subtly tints the app. Light and dark each have their own. System uses whichever is active."
themeAccentLight = "Light mode accent colour"
themeDark = "Dark"
themeDescription = "Choose light, dark, or follow your system"
themeDescription = "Choose light, dark, or follow your system so it switches automatically."
themeLight = "Light"
themeReset = "Restore theme to default"
themeSystem = "System"
title = "General"
user = "User"
+518
View File
@@ -0,0 +1,518 @@
#!/usr/bin/env node
// Theme colour lint. Modes:
//
// node theme-lint.mjs enforce: literal colours live ONLY in
// primitives.css; colors/dimensions must
// reference tokens; no duplicate primitives.
// Scope: core/theme/. (blocking)
// node theme-lint.mjs css-colors enforce: NO hardcoded colour in any source
// .css (primitives.css + generated output.css
// exempt). Scope: all editor/src. (blocking)
// node theme-lint.mjs code-colors enforce: no hardcoded colour in TS/TSX DOM
// code. Default-deny with layered exemptions
// (structural; var()/readColor/canvas/pdf-lib
// contexts; `// theme-allow-color` opt-out;
// exempt PATHS for rendering/vendor/config).
// Scope: all editor/src. (blocking)
// node theme-lint.mjs contrast enforce: text-on-surface / on-primary pairs
// clear WCAG AA per theme (default accent).
// (blocking)
//
// Structural black / white / transparent (shadows, scrims) are always allowed.
import { readFileSync, readdirSync } from "node:fs";
import { execSync } from "node:child_process";
import { relative, resolve, join } from "node:path";
const THEME = resolve(process.cwd(), "editor/src/core/theme");
const PRIMITIVES = "editor/src/core/theme/primitives.css";
// Fixed list of theme CSS files to check, so every read takes a constant path
// (no directory-listing feeding into a file read). readdir is used only to fail
// if a new .css is added without being registered — coverage can't silently
// lapse — but its output is never passed to readFileSync.
const THEME_FILES = [
"primitives.css",
"colors.css",
"dimensions.css",
"index.css",
];
// ── colour helpers ─────────────────────────────────────────────────────────
const HEX_RE = /#[0-9a-fA-F]{3,8}\b/g;
const FUNC_RE = /\b(?:rgba?|hsla?)\(\s*[^)]*\)/g;
const NAMED_RE =
/\b(?:white|black|red|green|blue|orange|yellow|purple|gray|grey|silver|transparent)\b/g;
function expandHex(hex) {
let h = hex.slice(1).toLowerCase();
if (h.length === 3) h = [...h].map((c) => c + c).join("");
if (h.length === 4) h = [...h].map((c) => c + c).join("");
return "#" + h;
}
function normalizeColor(raw) {
const s = raw.trim().toLowerCase();
if (s.startsWith("#")) return expandHex(s);
const nums = s.match(/[\d.]+%?/g);
if (!nums) return s;
return `${s.startsWith("hsl") ? "hsl" : "rgb"}(${nums.join(",")})`;
}
function isStructuralColor(norm) {
return (
norm === "transparent" ||
/^#000000(00)?$/.test(norm) ||
/^#ffffff(ff)?$/.test(norm) ||
/^rgb\(0,0,0[,)]/.test(norm) ||
/^rgb\(255,255,255[,)]/.test(norm)
);
}
function isStructuralName(name) {
return /^(?:white|black|transparent)$/i.test(name.trim());
}
function stripComments(text) {
return text.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "));
}
// ── enforce: literals only in primitives.css, no duplicate primitives ────────
function check() {
const violations = [];
const primitiveValues = new Map();
const lineOf = (text, index) => text.slice(0, index).split("\n").length;
// Fail if a theme .css exists that isn't registered above (readdir is only
// compared here — never used to build a path passed to readFileSync).
const known = new Set(THEME_FILES);
for (const name of readdirSync(THEME)) {
if (name.endsWith(".css") && !known.has(name)) {
violations.push({
file: relative(process.cwd(), join(THEME, name)),
line: 1,
msg: `unregistered theme CSS — add "${name}" to THEME_FILES in theme-lint.mjs`,
});
}
}
for (const name of THEME_FILES) {
const rel = relative(process.cwd(), join(THEME, name));
const isPrimitives = rel === PRIMITIVES;
const text = stripComments(readFileSync(join(THEME, name), "utf8"));
for (const re of [HEX_RE, FUNC_RE]) {
re.lastIndex = 0;
let m;
while ((m = re.exec(text)) !== null) {
const norm = normalizeColor(m[0]);
if (isStructuralColor(norm)) continue;
if (isPrimitives) {
if (primitiveValues.has(norm)) {
violations.push({
file: rel,
line: lineOf(text, m.index),
msg: `duplicate primitive value ${norm} (also ${primitiveValues.get(norm)})`,
});
} else {
primitiveValues.set(norm, m[0]);
}
} else {
violations.push({
file: rel,
line: lineOf(text, m.index),
msg: `raw colour ${m[0]} — define it in primitives.css and use var()`,
});
}
}
}
// Named colours, only in value position.
if (!isPrimitives) {
text.split("\n").forEach((line, i) => {
const colon = line.indexOf(":");
if (colon < 0 || /[{}]/.test(line)) return;
// Property must be a lone identifier — a custom prop (--x) OR a standard
// property (color, border) — so `color: red` is checked, not just tokens,
// while selectors (`.foo:hover`) with a colon are skipped.
if (!/^\s*(?:--)?[a-z][a-z0-9-]*\s*$/i.test(line.slice(0, colon)))
return;
const value = line
.slice(colon + 1)
.replace(/--[a-z0-9-]+/gi, " ")
.replace(/url\([^)]*\)/g, " ")
.replace(/["'][^"']*["']/g, " ");
for (const nm of value.match(NAMED_RE) || []) {
if (isStructuralName(nm)) continue;
violations.push({
file: rel,
line: i + 1,
msg: `named colour "${nm}" — define it in primitives.css and use var()`,
});
}
});
}
}
return violations;
}
// ── contrast report (warn-only): resolve --c-* per theme, check legibility ───
function reportContrast() {
const primitivesCss = readFileSync(join(THEME, "primitives.css"), "utf8");
const colorsCss = readFileSync(join(THEME, "colors.css"), "utf8");
const primitives = {};
for (const m of primitivesCss.matchAll(
/(--p-[a-z0-9-]+)\s*:\s*(#[0-9a-fA-F]{3,8})\s*;/g,
))
primitives[m[1]] = m[2];
const blocks = [];
for (const m of colorsCss.matchAll(/([^{}]+)\{([^}]*)\}/g)) {
const decls = {};
for (const d of m[2].matchAll(/(--c-[a-z0-9-]+)\s*:\s*([^;]+);/g))
decls[d[1]] = d[2].trim();
if (Object.keys(decls).length)
blocks.push({ selector: m[1].trim(), decls });
}
// The editor always renders data-app-theme="custom"; the accent (--user-*) is
// injected at runtime, so seed the DEFAULT blue to resolve the custom tint
// statically. Themes = the ones that actually render: editor custom light/dark
// (data-app-theme="custom") and the portal's neutral dark (data-theme="dark").
const SEED = {
"--user-primary": "#3b82f6",
"--user-primary-on": "#ffffff",
"--user-accent-fg": "#3b82f6",
};
const pick = (re) => blocks.filter((b) => re.test(b.selector));
const lightBase = pick(/:root/);
const customBase = blocks.filter(
(b) =>
/app-theme="custom"/.test(b.selector) &&
!/color-scheme="dark"/.test(b.selector),
);
const customDark = pick(
/app-theme="custom"\]\[data-mantine-color-scheme="dark"/,
);
const midnight = pick(/data-theme="dark"/);
const themes = {
"editor light": [...lightBase, ...customBase],
"editor dark": [...lightBase, ...customBase, ...customDark],
"portal dark": [...lightBase, ...midnight],
};
const flatten = (list) =>
Object.assign({ ...SEED }, ...list.map((b) => b.decls));
const hexToRgb = (h) => {
h = h.replace("#", "");
if (h.length === 3)
h = h
.split("")
.map((c) => c + c)
.join("");
return {
r: parseInt(h.slice(0, 2), 16),
g: parseInt(h.slice(2, 4), 16),
b: parseInt(h.slice(4, 6), 16),
a: 1,
};
};
const over = (f, b) => ({
r: f.r * f.a + b.r * (1 - f.a),
g: f.g * f.a + b.g * (1 - f.a),
b: f.b * f.a + b.b * (1 - f.a),
a: 1,
});
const mix = (a, b, p) => ({
r: (a.r * p + b.r * (100 - p)) / 100,
g: (a.g * p + b.g * (100 - p)) / 100,
b: (a.b * p + b.b * (100 - p)) / 100,
a: 1,
});
// Resolve any token value: hex, rgb(a), var(--x[, fallback]) (--p-* → palette,
// else the theme map/seed), or color-mix(in srgb, A n%, B|transparent).
function resolveValue(v, t, seen) {
if (v == null) return null;
v = v.trim();
let m;
if (v.startsWith("#")) return hexToRgb(v);
if ((m = v.match(/^rgba?\(([^)]+)\)$/))) {
const n = m[1]
.split(/[,/\s]+/)
.map(Number)
.filter((x) => !Number.isNaN(x));
return { r: n[0], g: n[1], b: n[2], a: n[3] ?? 1 };
}
if ((m = v.match(/^var\(\s*(--[a-z0-9-]+)\s*(?:,\s*([\s\S]+))?\)$/))) {
return resolveVar(m[1], m[2], t, seen);
}
if ((m = v.match(/^color-mix\(in srgb,\s*(.+?)\s+(\d+)%\s*,\s*(.+)\)$/))) {
const a = resolveValue(m[1], t, seen);
if (!a) return null;
if (m[3].trim() === "transparent") return { ...a, a: +m[2] / 100 };
const b = resolveValue(m[3].trim(), t, seen);
return b ? mix(a, b, +m[2]) : null;
}
return null;
}
function resolveVar(name, fallback, t, seen) {
if (name.startsWith("--p-")) {
return primitives[name]
? hexToRgb(primitives[name])
: fallback
? resolveValue(fallback, t, seen)
: null;
}
if (!seen.has(name) && t[name] !== undefined) {
const next = new Set(seen).add(name);
const r = resolveValue(t[name], t, next);
if (r) return r;
}
return fallback ? resolveValue(fallback, t, seen) : null;
}
const resolve = (token, t) =>
resolveValue(t[token] ?? null, t, new Set([token]));
const lum = ({ r, g, b }) => {
const f = (v) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
};
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
};
const contrast = (t1, t2, t) => {
const surface = resolve("--c-surface", t);
let a = resolve(t1, t);
let b = resolve(t2, t);
if (!a || !b || !surface) return null;
if (a.a < 1) a = over(a, surface);
if (b.a < 1) b = over(b, surface);
const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
};
const PAIRS = [
["--c-text", "--c-surface", 4.5],
["--c-text-muted", "--c-surface", 4.5],
["--c-text-subtle", "--c-surface", 4.5],
["--c-text", "--c-bg", 4.5],
["--c-text-on-primary", "--c-primary", 3.0],
];
let warnings = 0;
console.log("contrast report (warning-only, default accent):\n");
for (const name of Object.keys(themes)) {
const t = flatten(themes[name]);
console.log(` ${name}`);
for (const [t1, t2, floor] of PAIRS) {
const r = contrast(t1, t2, t);
if (r == null) {
console.log(` ? ${t1} on ${t2} (unresolved)`);
continue;
}
if (r < floor) warnings++;
console.log(
` ${r < floor ? "⚠ " : " "}${r.toFixed(2).padStart(5)} (floor ${floor}) ${t1} on ${t2}`,
);
}
console.log("");
}
console.log(
warnings
? `✖ ${warnings} pair(s) below floor.`
: "✓ all pairs clear their floor.",
);
return warnings;
}
// ── css-colors (blocking): no hardcoded colour in ANY source .css ────────────
// App-wide guard that source CSS routes every colour through the palette. The
// file list comes from `git ls-files` (a VCS query over tracked, in-repo paths)
// — never a directory walk feeding a read — so there's no readdir→readFile path.
// primitives.css (the literal home) and generated output.css are exempt.
function checkAppCss() {
const EXEMPT = /(?:^|\/)(?:primitives\.css|output\.css)$/;
const listed = execSync("git ls-files -- editor/src", {
encoding: "utf8",
})
.split("\n")
.map((l) => l.trim())
.filter((l) => l && l.endsWith(".css") && !EXEMPT.test(l));
const violations = [];
const lineOf = (text, index) => text.slice(0, index).split("\n").length;
for (const rel of listed) {
const text = stripComments(readFileSync(rel, "utf8"));
for (const re of [HEX_RE, FUNC_RE]) {
re.lastIndex = 0;
let m;
while ((m = re.exec(text)) !== null) {
if (/var\(/i.test(m[0])) continue; // rgb()/color-mix wrapping a token
const norm = normalizeColor(m[0]);
if (isStructuralColor(norm)) continue;
violations.push({
file: rel,
line: lineOf(text, m.index),
msg: `raw colour ${m[0]} — define it in primitives.css and use var(--p-…)`,
});
}
}
text.split("\n").forEach((line, i) => {
const colon = line.indexOf(":");
if (colon < 0 || /[{}]/.test(line)) return;
if (!/^\s*(?:--)?[a-z][a-z0-9-]*\s*$/i.test(line.slice(0, colon))) return;
const value = line
.slice(colon + 1)
.replace(/--[a-z0-9-]+/gi, " ")
.replace(/url\([^)]*\)/g, " ")
.replace(/["'][^"']*["']/g, " ");
for (const nm of value.match(NAMED_RE) || []) {
if (isStructuralName(nm)) continue;
violations.push({
file: rel,
line: i + 1,
msg: `named colour "${nm}" — define it in primitives.css and use var(--p-…)`,
});
}
});
}
return violations;
}
// ── code-colors (blocking): no NEW hardcoded colour in TS/TSX DOM code ───────
// TS/TSX can't be zeroed like CSS — canvas/PDF/pdfium rendering, colour maths,
// colour pickers, vendor brand and self-contained docs legitimately need
// numeric colour. So this is default-deny with layered exemptions:
// 1. structural black/white/transparent
// 2. detected safe CONTEXTS on the line (var()/readColor fallback, canvas
// assignment, pdf-lib 0..1 rgb) and an explicit `// theme-allow-color` opt-out
// 3. exempt PATHS — whole areas where colour literals are inherent
// Anything else (a raw colour in a normal DOM component) fails. File list from
// `git ls-files` (no directory walk). Comments and HTML entities are stripped.
const CODE_EXEMPT_PATH = [
/Thumbnail|Overlay|DrawingCanvas|PageEditor|MobileScannerPage/, // canvas/PDF render
/[Pp]df|PDF|pixelCompare|\/compare\.ts$|customPrimary|accentColors/,
/validateSignature\/outputtedPDFSections|CenteredMessageSection|StatusBadgeSection/,
/\/viewer\/|Annotation|useViewerReadAloud|CommentsSidebar|\/constants\/search\.ts$|SignaturePreview/,
/ColorPicker|ColorControl|WatchedFolderManagementModal|watchedFolderPresets|fileColors|unifiedBackground|folder\.ts$|policyFolders/, // pickers/palettes as data
/OAuthButtons|oauthCallbackHtml/, // vendor brand / self-contained doc
/mantineTheme|\/theme\.ts$|toolsTaxonomy|LayoutPreview|PageNumberPreview|CloudStorageIcons/, // theme/config objects
/\/onboarding\//, // decorative onboarding illustration gradients
/addStamp|addWatermark|\/tooltips\//, // colour params fed to PDF + tooltip prose
/UpgradeBanner|AdminPlanSection/, // bespoke marketing/upsell banner palette
/\.test\.[jt]sx?$|\.stories\.[jt]sx?$|\/types\//, // tests, stories, type decls
];
const CODE_HEX =
/#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})(?![0-9a-fA-F])/g;
const CODE_RGB = /rgba?\(([^)]*)\)/gi;
const codeStructuralHex = (h) =>
/^#(?:000|fff|000f|ffff|000000|ffffff|00000000|ffffffff)$/i.test(h);
const CODE_SKIP_LINE =
/theme-allow-color|var\(\s*--[a-z0-9-]+\s*,|readColor\(|getPropertyValue|\.colors\.[a-z]+\?\.\[|fillStyle|strokeStyle|shadowColor|addColorStop|createLinearGradient|ctx\.|getContext/i;
function codeStripNonCode(text) {
return text
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(
/(^|[^:])\/\/[^\n]*/g,
(m, p) => p + m.slice(p.length).replace(/[^\n]/g, " "),
)
.replace(/&#\d+;/g, " ");
}
function codeRgbIsColour(inner) {
if (/var\(/.test(inner)) return false;
const p = inner
.split(/[, /]+/)
.map((x) => x.trim())
.filter(Boolean);
if (!/^\d/.test(p[0] || "")) return false;
const nums = p.slice(0, 3).map(Number);
if (nums.some((n) => Number.isNaN(n))) return false;
if (nums.every((n) => n <= 1)) return false; // pdf-lib rgb(0..1)
const k = `${nums[0]},${nums[1]},${nums[2]}`;
return k !== "0,0,0" && k !== "255,255,255";
}
function checkCodeColors() {
const files = execSync("git ls-files -- editor/src", { encoding: "utf8" })
.split("\n")
.map((l) => l.trim())
.filter(
(l) =>
/\.(ts|tsx)$/.test(l) && !CODE_EXEMPT_PATH.some((re) => re.test(l)),
);
const violations = [];
for (const rel of files) {
const raw = readFileSync(rel, "utf8");
const rawLines = raw.split("\n");
const text = codeStripNonCode(raw);
text.split("\n").forEach((line, i) => {
// marker lives in a comment (stripped from `line`), so test the raw line
if (/theme-allow-color/.test(rawLines[i] || "")) return;
if (CODE_SKIP_LINE.test(line)) return;
const hits = [];
for (const h of line.match(CODE_HEX) || [])
if (!codeStructuralHex(h)) hits.push(h);
for (const m of line.match(CODE_RGB) || [])
if (codeRgbIsColour(m.replace(/rgba?\(|\)/gi, ""))) hits.push(m);
for (const h of hits)
violations.push({
file: rel,
line: i + 1,
msg: `hardcoded colour ${h} — use a var(--c-*/--p-*) token, or add \`// theme-allow-color <reason>\` if this must be a literal`,
});
});
}
return violations;
}
// ── CLI ──────────────────────────────────────────────────────────────────────
if (process.argv.includes("contrast")) {
const below = reportContrast();
if (below) {
console.error(
`\nRaise the failing token's lightness (or its surface's) until every pair clears WCAG AA (4.5 normal / 3.0 on-primary).\n`,
);
process.exit(1);
}
process.exit(0);
}
if (process.argv.includes("code-colors")) {
const violations = checkCodeColors();
if (violations.length) {
console.error(
`\n✖ theme-lint code-colors: ${violations.length} hardcoded colour(s) in TS/TSX:\n`,
);
for (const v of violations)
console.error(` ${v.file}:${v.line} ${v.msg}`);
console.error("");
process.exit(1);
}
console.log(
"✓ theme-lint code-colors: no hardcoded colour in TS/TSX DOM code (rendering/vendor/config areas exempt)",
);
process.exit(0);
}
if (process.argv.includes("css-colors")) {
const violations = checkAppCss();
if (violations.length) {
console.error(
`\n✖ theme-lint css-colors: ${violations.length} hardcoded colour(s) in source CSS:\n`,
);
for (const v of violations)
console.error(` ${v.file}:${v.line} ${v.msg}`);
console.error(
`\nDefine every colour once in core/theme/primitives.css and reference it with var(--p-…).\n`,
);
process.exit(1);
}
console.log(
"✓ theme-lint css-colors: source CSS is free of hardcoded colour",
);
process.exit(0);
}
const violations = check();
if (violations.length) {
console.error(
`\n✖ theme-lint: ${violations.length} raw/duplicate colour(s) in core/theme/:\n`,
);
for (const v of violations) console.error(` ${v.file}:${v.line} ${v.msg}`);
console.error(
`\nDefine every colour once in core/theme/primitives.css and reference it with var(--p-…).\n`,
);
process.exit(1);
}
console.log(
"✓ theme-lint: core/theme colours all route through the primitive palette",
);
@@ -8,7 +8,7 @@
}
.freeHighlight {
background: linear-gradient(135deg, #6366f1, #ec4899);
background: linear-gradient(135deg, var(--p-indigo-500), var(--p-pink-500));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
@@ -36,7 +36,7 @@
flex-direction: column;
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border-default, #d1d5db);
border: 1px solid var(--c-border);
border-radius: 12px;
margin-bottom: 12px;
}
@@ -49,7 +49,7 @@
}
.memberRow + .memberRow {
border-top: 1px solid var(--border-subtle, #e5e7eb);
border-top: 1px solid var(--c-border-subtle);
}
.memberIdentity {
@@ -62,7 +62,7 @@
.memberName {
font-size: 14px;
font-weight: 600;
color: var(--onboarding-title, #111827);
color: var(--c-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -70,7 +70,7 @@
.memberEmail {
font-size: 12px;
color: var(--onboarding-body, #6b7280);
color: var(--c-text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -87,7 +87,7 @@ export function FreeLimitReachedModal({ onClose }: FreeLimitReachedModalProps) {
content: {
overflow: "hidden",
border: "none",
background: "var(--bg-surface)",
background: "var(--c-surface)",
maxHeight: "90vh",
display: "flex",
flexDirection: "column",
@@ -87,7 +87,7 @@ export function SpendCapReachedModal({ onClose }: SpendCapReachedModalProps) {
content: {
overflow: "hidden",
border: "none",
background: "var(--bg-surface)",
background: "var(--c-surface)",
maxHeight: "90vh",
display: "flex",
flexDirection: "column",
@@ -2,25 +2,22 @@
Uses the app's semantic theme tokens (theme.css) so it tracks light/dark. */
.payg {
--payg-accent: #0a8bff;
--payg-accent-soft: rgba(10, 139, 255, 0.1);
--payg-accent: var(--p-azure-500);
--payg-accent-soft: color-mix(in srgb, var(--p-azure-500) 10%, transparent);
--payg-radius: 14px;
--payg-gap: 20px;
/* Cards sit ON the modal content bg; in light that's white-on-white so we
lean on border + shadow. Tokens are overridden per scheme below. */
--payg-card-bg: var(--bg-surface);
--payg-card-border: var(--border-default);
--payg-inset-bg: var(--bg-raised);
--payg-divider: var(--border-subtle);
--payg-card-bg: var(--c-surface);
--payg-card-border: var(--c-border);
--payg-inset-bg: var(--c-surface-raised);
--payg-divider: var(--c-border-subtle);
}
/* Dark mode: modal content bg is #131729 (--bg-surface), so lift cards above it */
/* Dark mode: surfaces/borders track the neutral --c-* tokens (set on the base
rule); only the brand-azure accent tint is tuned brighter for dark. */
[data-mantine-color-scheme="dark"] .payg {
--payg-card-bg: #1c2340;
--payg-card-border: #2d3560;
--payg-inset-bg: #0d1020;
--payg-accent-soft: rgba(79, 142, 245, 0.16);
--payg-divider: #2d3560;
--payg-accent-soft: color-mix(in srgb, var(--p-azure-550) 16%, transparent);
}
/* ── Header ─────────────────────────────────────────────────────────── */
@@ -29,12 +26,12 @@
font-size: 1.35rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--text-primary);
color: var(--c-text);
}
.payg-header__subtitle {
margin-top: 4px;
font-size: 0.875rem;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.payg-role-pill {
display: inline-flex;
@@ -49,12 +46,12 @@
.payg-role-pill[data-leader="true"] {
background: var(--payg-accent-soft);
color: var(--payg-accent);
border: 1px solid rgba(10, 139, 255, 0.25);
border: 1px solid color-mix(in srgb, var(--p-azure-500) 25%, transparent);
}
.payg-role-pill[data-leader="false"] {
background: var(--bg-muted);
color: var(--text-muted);
border: 1px solid var(--border-default);
background: var(--c-surface-sunken);
color: var(--c-text-subtle);
border: 1px solid var(--c-border);
}
/* ── Plan header: free-vs-metered split (shared by free + subscribed) ──── */
@@ -73,7 +70,7 @@
}
.payg-planhead__eyebrow {
font-size: 0.78rem;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.payg-planhead__split {
display: grid;
@@ -98,7 +95,7 @@
margin-bottom: 8px;
}
.payg-planhead__lbl--free {
color: #10b981;
color: var(--p-emerald-500);
}
.payg-planhead__lbl--meter {
color: var(--payg-accent);
@@ -110,14 +107,14 @@
margin: 0;
font-size: 1.05rem;
font-weight: 700;
color: var(--text-primary);
color: var(--c-text);
letter-spacing: -0.01em;
line-height: 1.25;
}
.payg-planhead__body {
margin: 5px 0 0;
font-size: 0.85rem;
color: var(--text-muted);
color: var(--c-text-subtle);
line-height: 1.5;
}
@media (max-width: 640px) {
@@ -147,11 +144,11 @@
.payg-card__title {
font-size: 0.95rem;
font-weight: 650;
color: var(--text-primary);
color: var(--c-text);
}
.payg-card__subtitle {
font-size: 0.8125rem;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-top: 2px;
}
@@ -175,22 +172,22 @@
.payg-hero[data-state="FULL"]::before {
background: linear-gradient(
135deg,
rgba(10, 139, 255, 0.12) 0%,
rgba(10, 139, 255, 0) 55%
color-mix(in srgb, var(--p-azure-500) 12%, transparent) 0%,
color-mix(in srgb, var(--p-azure-500) 0%, transparent) 55%
);
}
.payg-hero[data-state="WARNED"]::before {
background: linear-gradient(
135deg,
rgba(234, 179, 8, 0.16) 0%,
rgba(234, 179, 8, 0) 55%
color-mix(in srgb, var(--p-amber-400) 16%, transparent) 0%,
color-mix(in srgb, var(--p-amber-400) 0%, transparent) 55%
);
}
.payg-hero[data-state="DEGRADED"]::before {
background: linear-gradient(
135deg,
rgba(239, 68, 68, 0.16) 0%,
rgba(239, 68, 68, 0) 55%
color-mix(in srgb, var(--p-red-500) 16%, transparent) 0%,
color-mix(in srgb, var(--p-red-500) 0%, transparent) 55%
);
}
.payg-hero__inner {
@@ -202,7 +199,7 @@
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.payg-hero__figure {
display: flex;
@@ -215,12 +212,12 @@
font-weight: 750;
line-height: 1;
letter-spacing: -0.03em;
color: var(--text-primary);
color: var(--c-text);
font-variant-numeric: tabular-nums;
}
.payg-hero__cap {
font-size: 1rem;
color: var(--text-muted);
color: var(--c-text-subtle);
font-variant-numeric: tabular-nums;
}
.payg-hero__meta {
@@ -229,10 +226,10 @@
flex-wrap: wrap;
gap: 6px 18px;
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--c-text-muted);
}
.payg-hero__meta-dot {
color: var(--border-strong);
color: var(--c-border-strong);
}
.payg-hero__credit {
display: inline-flex;
@@ -242,11 +239,11 @@
font-size: 0.75rem;
font-weight: 600;
color: var(--color-green-700);
background: rgba(34, 197, 94, 0.14);
background: color-mix(in srgb, var(--p-green-500) 14%, transparent);
}
[data-mantine-color-scheme="dark"] .payg-hero__credit {
color: #4ade80;
background: rgba(34, 197, 94, 0.18);
color: var(--p-green-400);
background: color-mix(in srgb, var(--p-green-500) 18%, transparent);
}
/* Make Mantine default-variant buttons read clearly on the lifted cards in
@@ -257,16 +254,24 @@
/* Dark mode: the stock default button is a flat slab that disappears into the
card. Lift it with a soft top-down gradient + brighter border, and warm the
hover so the buttons feel tactile against --payg-card-bg (#1c2340). Driven
through Mantine's --button-* vars. The same rule covers disabled buttons
hover so the buttons feel tactile against the card surface. Driven through
Mantine's --button-* vars. The same rule covers disabled buttons
(Cancel/Update cap before the form is dirty) so they read identically to the
always-enabled ones — Mantine only fades them via opacity. */
[data-mantine-color-scheme="dark"]
.payg
.mantine-Button-root[data-variant="default"] {
--button-bg: linear-gradient(180deg, #232a50 0%, #1c2340 100%);
--button-hover: linear-gradient(180deg, #2d3560 0%, #232a50 100%);
--button-bd: 1px solid #2d3560;
--button-bg: linear-gradient(
180deg,
var(--c-surface-raised) 0%,
var(--c-surface) 100%
);
--button-hover: linear-gradient(
180deg,
var(--c-border) 0%,
var(--c-surface-raised) 100%
);
--button-bd: 1px solid var(--c-border);
--button-color: var(--mantine-color-white);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.04) inset,
@@ -307,7 +312,7 @@
}
.payg-status[data-state="FULL"] .payg-status__dot {
background: var(--color-green-500);
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--p-green-500) 18%, transparent);
}
.payg-status[data-state="WARNED"] {
background: var(--color-yellow-100);
@@ -315,7 +320,7 @@
}
.payg-status[data-state="WARNED"] .payg-status__dot {
background: var(--color-yellow-500);
box-shadow: 0 0 0 3px rgba(234, 179, 8, 0.2);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--p-amber-400) 20%, transparent);
}
.payg-status[data-state="DEGRADED"] {
background: var(--color-red-100);
@@ -323,7 +328,7 @@
}
.payg-status[data-state="DEGRADED"] .payg-status__dot {
background: var(--color-red-500);
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--p-red-500) 20%, transparent);
}
/* Segmented usage bar */
@@ -331,7 +336,7 @@
margin-top: 18px;
height: 10px;
border-radius: 999px;
background: var(--bg-muted);
background: var(--c-surface-sunken);
overflow: hidden;
position: relative;
}
@@ -341,13 +346,13 @@
transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.payg-bar__fill[data-state="FULL"] {
background: linear-gradient(90deg, #0a8bff, #38bdf8);
background: linear-gradient(90deg, var(--p-azure-500), var(--p-azure-400));
}
.payg-bar__fill[data-state="WARNED"] {
background: linear-gradient(90deg, #f59e0b, #fbbf24);
background: linear-gradient(90deg, var(--p-amber-500), var(--p-amber-400));
}
.payg-bar__fill[data-state="DEGRADED"] {
background: linear-gradient(90deg, #dc2626, #f87171);
background: linear-gradient(90deg, var(--p-red-600), var(--p-red-400));
}
/* ── "What counts as a document?" expandable help ───────────────────── */
@@ -365,10 +370,10 @@
font: inherit;
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-secondary);
color: var(--c-text-muted);
}
.payg-help__toggle:hover {
color: var(--text-primary);
color: var(--c-text);
}
.payg-help__chevron {
transition: transform 0.15s ease;
@@ -381,10 +386,10 @@
padding: 12px 14px;
border-radius: 10px;
border: 1px solid var(--payg-card-border);
background: var(--bg-muted);
background: var(--c-surface-sunken);
font-size: 0.8125rem;
line-height: 1.45;
color: var(--text-secondary);
color: var(--c-text-muted);
}
.payg-help__panel ul {
margin: 0;
@@ -401,7 +406,7 @@
padding: 13px 16px;
border-radius: 10px;
background: var(--payg-accent-soft);
border: 1px solid rgba(10, 139, 255, 0.2);
border: 1px solid color-mix(in srgb, var(--p-azure-500) 20%, transparent);
}
.payg-preview__icon {
color: var(--payg-accent);
@@ -409,12 +414,12 @@
}
.payg-preview__main {
font-size: 0.875rem;
color: var(--text-primary);
color: var(--c-text);
font-weight: 550;
}
.payg-preview__note {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-top: 1px;
}
@@ -453,17 +458,17 @@
flex-shrink: 0;
}
.payg-gate[data-enabled="true"] .payg-gate__chip {
background: rgba(34, 197, 94, 0.16);
color: #22c55e;
background: color-mix(in srgb, var(--p-green-500) 16%, transparent);
color: var(--p-green-500);
}
.payg-gate[data-enabled="false"] .payg-gate__chip {
background: rgba(239, 68, 68, 0.16);
color: #f87171;
background: color-mix(in srgb, var(--p-red-500) 16%, transparent);
color: var(--p-red-400);
}
.payg-gate__label {
font-size: 0.8125rem;
line-height: 1.35;
color: var(--text-primary);
color: var(--c-text);
flex: 1;
}
.payg-gate__tag {
@@ -477,16 +482,16 @@
white-space: nowrap;
}
.payg-gate__tag[data-variant="on"] {
color: var(--text-muted);
background: var(--bg-muted);
color: var(--c-text-subtle);
background: var(--c-surface-sunken);
}
.payg-gate__tag[data-variant="pause"] {
color: #dc2626;
background: rgba(239, 68, 68, 0.12);
color: var(--c-danger);
background: color-mix(in srgb, var(--p-red-500) 12%, transparent);
}
[data-mantine-color-scheme="dark"] .payg-gate__tag[data-variant="pause"] {
color: #f87171;
background: rgba(239, 68, 68, 0.18);
color: var(--c-danger);
background: color-mix(in srgb, var(--p-red-500) 18%, transparent);
}
/* ── Member rows ────────────────────────────────────────────────────── */
@@ -515,11 +520,11 @@
.payg-member__name {
font-size: 0.875rem;
font-weight: 550;
color: var(--text-primary);
color: var(--c-text);
}
.payg-member__email {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.payg-member__usage {
text-align: right;
@@ -527,7 +532,7 @@
}
.payg-member__usage-num {
font-size: 0.8125rem;
color: var(--text-primary);
color: var(--c-text);
font-variant-numeric: tabular-nums;
}
.payg-member__minibar {
@@ -570,27 +575,27 @@
}
.payg-activity__label {
font-size: 0.8438rem;
color: var(--text-primary);
color: var(--c-text);
flex: 1;
}
.payg-activity__ts {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.payg-activity__kind {
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-muted);
background: var(--bg-muted);
color: var(--c-text-subtle);
background: var(--c-surface-sunken);
padding: 2px 8px;
border-radius: 999px;
}
.payg-activity__units {
font-size: 0.8125rem;
font-weight: 650;
color: var(--text-primary);
color: var(--c-text);
font-variant-numeric: tabular-nums;
min-width: 58px;
text-align: right;
@@ -604,20 +609,20 @@
gap: 16px;
padding: 20px 24px;
border-radius: var(--payg-radius);
border: 1px solid rgba(99, 91, 255, 0.28);
border: 1px solid color-mix(in srgb, var(--p-periwinkle-500) 28%, transparent);
background: linear-gradient(
135deg,
rgba(99, 91, 255, 0.1) 0%,
rgba(99, 91, 255, 0.02) 100%
color-mix(in srgb, var(--p-periwinkle-500) 10%, transparent) 0%,
color-mix(in srgb, var(--p-periwinkle-500) 2%, transparent) 100%
);
}
.payg-stripe__title {
font-size: 0.9375rem;
font-weight: 650;
color: var(--text-primary);
color: var(--c-text);
}
.payg-stripe__subtitle {
font-size: 0.8125rem;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-top: 2px;
}
@@ -60,12 +60,12 @@ interface PaygProps {
// Stable-ish avatar colour from a string.
const AVATAR_COLORS = [
"#0a8bff",
"#8b5cf6",
"#ec4899",
"#10b981",
"#f59e0b",
"#06b6d4",
"var(--p-azure-500)",
"var(--p-violet-500)",
"var(--p-pink-500)",
"var(--p-emerald-500)",
"var(--p-amber-500)",
"var(--p-cyan-500)",
];
function avatarColor(seed: string): string {
let h = 0;
@@ -70,11 +70,11 @@
gap: 8px;
font-size: 0.8rem;
line-height: 1.45;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.paygf-proc__membernote-icon {
flex-shrink: 0;
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 1.1rem !important;
margin-top: 1px;
}
@@ -113,12 +113,12 @@
font-weight: 750;
line-height: 1;
letter-spacing: -0.02em;
color: var(--text-primary);
color: var(--c-text);
font-variant-numeric: tabular-nums;
}
.paygf-meter__cap {
font-size: 0.85rem;
color: var(--text-muted);
color: var(--c-text-subtle);
font-variant-numeric: tabular-nums;
}
.paygf-meter .payg-bar {
@@ -131,7 +131,7 @@
align-items: center;
gap: 6px 10px;
font-size: 0.78rem;
color: var(--text-secondary);
color: var(--c-text-muted);
}
/* ── Leader CTA card ──────────────────────────────────────────────────── */
@@ -145,8 +145,10 @@
border: 1.5px solid transparent;
background:
linear-gradient(var(--payg-card-bg), var(--payg-card-bg)) padding-box,
linear-gradient(135deg, var(--payg-accent) 0%, #6c5ce7 100%) border-box;
box-shadow: 0 10px 28px -18px rgba(10, 139, 255, 0.4);
linear-gradient(135deg, var(--payg-accent) 0%, var(--p-purple-500) 100%)
border-box;
box-shadow: 0 10px 28px -18px
color-mix(in srgb, var(--p-azure-500) 40%, transparent);
display: flex;
flex-direction: column;
gap: 16px;
@@ -161,7 +163,11 @@
flex-shrink: 0;
padding: 10px;
border-radius: 12px;
background: linear-gradient(135deg, var(--payg-accent) 0%, #6c5ce7 100%);
background: linear-gradient(
135deg,
var(--payg-accent) 0%,
var(--p-purple-500) 100%
);
color: white !important;
font-size: 1.7rem !important;
}
@@ -172,14 +178,14 @@
margin: 0;
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary);
color: var(--c-text);
letter-spacing: -0.01em;
line-height: 1.25;
}
.paygf-cta__subtitle {
margin: 4px 0 0;
font-size: 0.9rem;
color: var(--text-muted);
color: var(--c-text-subtle);
line-height: 1.5;
}
@@ -196,7 +202,7 @@
align-items: flex-start;
gap: 8px;
font-size: 0.85rem;
color: var(--text-primary);
color: var(--c-text);
line-height: 1.45;
}
.paygf-cta__check {
@@ -220,7 +226,7 @@
.paygf-cta__reassurance {
margin: 0;
font-size: 0.78rem;
color: var(--text-muted);
color: var(--c-text-subtle);
text-align: right;
}
@@ -237,7 +243,7 @@
}
.paygf-member-note__icon {
flex-shrink: 0;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 8px;
background: var(--payg-card-bg);
border-radius: 10px;
@@ -247,14 +253,14 @@
margin: 0;
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
color: var(--c-text);
letter-spacing: -0.005em;
line-height: 1.3;
}
.paygf-member-note__body {
margin: 6px 0 0;
font-size: 0.875rem;
color: var(--text-muted);
color: var(--c-text-subtle);
line-height: 1.5;
}
@@ -285,14 +291,14 @@
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-bottom: 10px;
}
.paygf-explainer__icon {
font-size: 1rem !important;
}
.paygf-explainer__icon--free {
color: #10b981;
color: var(--p-emerald-500);
}
.paygf-explainer__icon--paid {
color: var(--payg-accent);
@@ -300,6 +306,6 @@
.paygf-explainer__text {
margin: 0;
font-size: 0.85rem;
color: var(--text-primary);
color: var(--c-text);
line-height: 1.55;
}
@@ -9,12 +9,12 @@
Payg.css / UpgradeModal.css treatments the control sits beside. */
.scc {
--scc-accent: #0a8bff;
--scc-accent-text: #0a8bff;
--scc-accent-soft: rgba(10, 139, 255, 0.12);
--scc-accent-border: rgba(10, 139, 255, 0.25);
--scc-chip-bg: var(--bg-muted);
--scc-chip-border: var(--border-default);
--scc-accent: var(--p-azure-500);
--scc-accent-text: var(--p-azure-500);
--scc-accent-soft: color-mix(in srgb, var(--p-azure-500) 12%, transparent);
--scc-accent-border: color-mix(in srgb, var(--p-azure-500) 25%, transparent);
--scc-chip-bg: var(--c-surface-sunken);
--scc-chip-border: var(--c-border);
/* Consistent vertical rhythm between the control row, the estimate, and any
note — without it the blocks sit flush. */
display: flex;
@@ -22,10 +22,10 @@
gap: 14px;
}
[data-mantine-color-scheme="dark"] .scc {
--scc-accent-text: #7ab4ff;
--scc-accent-soft: rgba(79, 142, 245, 0.16);
--scc-chip-bg: #1c2340;
--scc-chip-border: #2d3560;
/* Chip surface/border track the neutral --c-* tokens (base rule); only the
brand-azure accent is tuned brighter for dark. */
--scc-accent-text: var(--p-azure-300);
--scc-accent-soft: color-mix(in srgb, var(--p-azure-500) 16%, transparent);
}
/* ── Inline row: presets · custom · no-cap · (save) ──────────────────── */
@@ -47,7 +47,7 @@
border-radius: 999px;
border: 1px solid var(--scc-chip-border);
background: var(--scc-chip-bg);
color: var(--text-secondary);
color: var(--c-text-muted);
font: inherit;
font-size: 0.85rem;
font-weight: 600;
@@ -59,8 +59,8 @@
background 0.12s ease;
}
.scc-chip:hover:not(:disabled) {
color: var(--text-primary);
border-color: var(--border-strong);
color: var(--c-text);
border-color: var(--c-border-strong);
}
.scc-chip[data-selected="true"] {
background: var(--scc-accent-soft);
@@ -90,7 +90,7 @@
background 0.12s ease;
}
.scc-custom:hover {
border-color: var(--border-strong);
border-color: var(--c-border-strong);
}
.scc-custom[data-active="true"] {
border-style: solid;
@@ -98,7 +98,7 @@
background: var(--scc-accent-soft);
}
.scc-custom__symbol {
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 0.85rem;
font-weight: 600;
}
@@ -111,14 +111,14 @@
border: none;
outline: none;
background: transparent;
color: var(--text-primary);
color: var(--c-text);
font: inherit;
font-size: 0.85rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.scc-custom__input::placeholder {
color: var(--text-muted);
color: var(--c-text-subtle);
font-weight: 600;
}
.scc-custom:disabled,
@@ -148,17 +148,17 @@
.scc-estimate__main {
font-size: 0.875rem;
font-weight: 550;
color: var(--text-primary);
color: var(--c-text);
}
.scc-estimate__sub {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-top: 1px;
}
/* Quiet helper line (e.g. "Shown in USD — change later in your currency"). */
.scc-note {
font-size: 0.8125rem;
color: var(--text-muted);
color: var(--c-text-subtle);
line-height: 1.45;
}
@@ -430,8 +430,8 @@ const TeamSection: React.FC = () => {
style={
member.role !== "LEADER"
? {
backgroundColor: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
backgroundColor: "var(--c-surface-raised)",
color: "var(--c-accent-fg)",
}
: undefined
}
@@ -5,22 +5,21 @@
user feels like they're filling out one form, not navigating pages. */
.upm {
--upm-accent: #0a8bff;
--upm-accent-2: #6c5ce7;
--upm-accent-soft: rgba(10, 139, 255, 0.08);
--upm-success: #10b981;
--upm-accent: var(--p-azure-500);
--upm-accent-2: var(--p-purple-500);
--upm-accent-soft: color-mix(in srgb, var(--p-azure-500) 8%, transparent);
--upm-success: var(--p-emerald-500);
--upm-radius: 18px;
--upm-card-bg: var(--bg-surface);
--upm-border: var(--border-default);
--upm-divider: var(--border-subtle);
--upm-text: var(--text-primary);
--upm-muted: var(--text-muted);
--upm-card-bg: var(--c-surface);
--upm-border: var(--c-border);
--upm-divider: var(--c-border-subtle);
--upm-text: var(--c-text);
--upm-muted: var(--c-text-subtle);
}
[data-mantine-color-scheme="dark"] .upm {
--upm-card-bg: #1c2340;
--upm-border: #2d3560;
--upm-divider: #2d3560;
--upm-accent-soft: rgba(79, 142, 245, 0.14);
/* Card/border/divider track the neutral --c-* tokens (base rule); only the
brand-azure accent tint is tuned for dark. */
--upm-accent-soft: color-mix(in srgb, var(--p-azure-550) 14%, transparent);
}
/* Backdrop locks the page and centres the modal. z-index matches
@@ -254,7 +253,7 @@
border-radius: 6px;
}
.upm-step__edit:hover {
background: rgba(10, 139, 255, 0.12);
background: color-mix(in srgb, var(--p-azure-500) 12%, transparent);
}
/* Placeholder card for the loading / error / mock states — a dashed, centered
@@ -327,8 +326,10 @@
}
.upm-confirm__icon {
font-size: 64px !important;
color: #10b981;
filter: drop-shadow(0 6px 18px rgba(16, 185, 129, 0.35));
color: var(--upm-success);
filter: drop-shadow(
0 6px 18px color-mix(in srgb, var(--p-emerald-500) 35%, transparent)
);
}
.upm-confirm__title {
margin: 6px 0 0;
@@ -61,7 +61,9 @@ function Swatch({ label, src, onDark, h }: Asset & { h: number }) {
>
<img src={src} alt={label} style={{ height: h, maxWidth: 200 }} />
</div>
<figcaption style={{ fontSize: 12, color: "var(--text-muted, #71717a)" }}>
<figcaption
style={{ fontSize: 12, color: "var(--c-text-subtle, #71717a)" }}
>
{label}
</figcaption>
</figure>
@@ -221,7 +221,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
width: "100%",
border: "none",
borderRadius: "var(--radius-md)",
backgroundColor: "var(--bg-file-manager)",
backgroundColor: "var(--c-bg)",
}}
styles={{
inner: { pointerEvents: "all" },
@@ -227,7 +227,7 @@ export const TextInputWithFont: React.FC<TextInputWithFontProps> = ({
width: 24,
height: 24,
backgroundColor: textColor,
border: "1px solid #ccc",
border: "1px solid var(--c-border)",
borderRadius: 4,
cursor: disabled ? "default" : "pointer",
}}
@@ -110,7 +110,7 @@ const AddFileCard = ({
variant="tertiary"
style={{
backgroundColor: "var(--landing-button-bg)",
color: "var(--landing-button-color)",
color: "var(--c-accent-fg)",
border: "1px solid var(--landing-button-border)",
borderRadius: "2rem",
height: "38px",
@@ -129,7 +129,7 @@ const AddFileCard = ({
icon="add"
width="1.5rem"
height="1.5rem"
className="text-[var(--accent-interactive)]"
className="text-[var(--c-primary)]"
/>
<span>{t("landing.addFiles", "Add Files")}</span>
</Button>
@@ -140,7 +140,7 @@ const AddFileCard = ({
title={terminology.uploadFromComputer}
style={{
backgroundColor: "var(--landing-button-bg)",
color: "var(--landing-button-color)",
color: "var(--c-accent-fg)",
border: "1px solid var(--landing-button-border)",
borderRadius: "1rem",
height: "38px",
@@ -161,7 +161,7 @@ const AddFileCard = ({
icon={icons.uploadIconName}
width="1.25rem"
height="1.25rem"
style={{ color: "var(--accent-interactive)", flexShrink: 0 }}
style={{ color: "var(--c-primary)", flexShrink: 0 }}
/>
{isUploadHover && (
<span
@@ -181,7 +181,7 @@ const AddFileCard = ({
{/* Instruction Text */}
<span
className="text-[var(--accent-interactive)]"
className="text-[var(--c-primary)]"
style={{
fontSize: ".8rem",
textAlign: "center",
@@ -3,7 +3,7 @@
========================= */
.card {
background: var(--file-card-bg);
background: var(--c-surface-raised);
border-radius: 0.0625rem;
cursor: pointer;
transition:
@@ -28,7 +28,7 @@
/* While dragging */
.card.dragging,
.card:global(.dragging) {
outline: 1px solid var(--border-strong);
outline: 1px solid var(--c-border-strong);
box-shadow: var(--shadow-md);
transform: none !important;
}
@@ -42,15 +42,15 @@
justify-content: space-between;
padding: 0 6px;
user-select: none;
background: var(--bg-toolbar);
color: var(--text-primary);
border-bottom: 1px solid var(--border-default);
background: var(--c-bg-raised);
color: var(--c-text);
border-bottom: 1px solid var(--c-border);
}
.headerResting {
background: #3b4b6e; /* dark blue for unselected in light mode */
background: var(--p-navy-500); /* dark blue for unselected in light mode */
color: #ffffff;
border-bottom: 1px solid var(--border-default);
border-bottom: 1px solid var(--c-border);
}
.headerSelected {
@@ -62,20 +62,20 @@
/* Error highlight (transient) */
.headerError {
background: var(--color-red-200);
color: var(--text-primary);
color: var(--c-text);
border-bottom: 2px solid var(--color-red-500);
}
/* Unsupported (but not errored) header appearance */
.headerUnsupported {
background: var(--unsupported-bar-bg); /* neutral gray */
background: var(--c-bg-raised); /* neutral gray */
color: #ffffff;
border-bottom: 1px solid var(--unsupported-bar-border);
border-bottom: 1px solid var(--c-border);
}
/* Selected border color in light mode */
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
outline-color: var(--card-selected-border);
outline-color: var(--c-primary);
}
/* Reserve space for checkbox instead of logo */
@@ -117,12 +117,12 @@
/* -------- Title / Meta -------- */
.title {
line-height: 1.2;
color: var(--text-primary);
color: var(--c-text);
}
.meta {
margin-top: 2px;
color: var(--text-secondary);
color: var(--c-text-muted);
}
/* -------- Preview area -------- */
@@ -131,7 +131,7 @@
width: 100%;
height: 100%;
overflow: hidden;
background: var(--file-card-bg);
background: var(--c-surface-raised);
}
.previewPaper {
@@ -142,7 +142,7 @@
display: grid;
position: relative;
overflow: hidden;
background: var(--file-card-bg);
background: var(--c-surface-raised);
}
/* Thumbnail fallback */
@@ -152,7 +152,7 @@
inset: 0;
display: grid;
place-items: center;
color: var(--text-secondary);
color: var(--c-text-muted);
font-weight: 600;
font-size: 12px;
}
@@ -162,7 +162,7 @@
position: absolute;
bottom: 6px;
right: 6px;
color: var(--text-secondary);
color: var(--c-text-muted);
z-index: 1;
cursor: grab;
display: inline-flex;
@@ -173,12 +173,12 @@
position: absolute;
left: 0;
top: 44px; /* just below header */
background: var(--bg-toolbar);
border-bottom: 1px solid var(--border-default);
background: var(--c-bg-raised);
border-bottom: 1px solid var(--c-border);
z-index: 20;
overflow: hidden;
animation: slideDown 140ms ease-out;
color: var(--text-primary);
color: var(--c-text);
}
@keyframes slideDown {
@@ -200,13 +200,13 @@
padding: 12px 16px;
background: transparent;
border: none;
color: var(--text-primary);
color: var(--c-text);
cursor: pointer;
text-align: left;
}
.actionRow:hover {
background: var(--hover-bg);
background: var(--c-hover);
}
.actionDanger {
@@ -215,7 +215,7 @@
.actionsDivider {
height: 1px;
background: var(--border-default);
background: var(--c-border);
margin: 4px 0;
}
@@ -229,13 +229,13 @@
}
.pinned {
color: #ffc107 !important;
color: var(--p-amber-400) !important;
}
/* Unsupported file indicator */
.unsupportedPill {
margin-left: 1.75rem;
background: #6b7280;
background: var(--p-gray-500);
color: white;
padding: 4px 8px;
border-radius: 12px;
@@ -291,36 +291,23 @@
DARK MODE OVERRIDES
========================= */
:global([data-mantine-color-scheme="dark"]) .card {
outline-color: #1c2340; /* deselected stroke */
outline-color: var(--c-border); /* deselected stroke (neutral) */
}
:global([data-mantine-color-scheme="dark"]) .card[data-selected="true"] {
outline-color: #2d3560; /* selected stroke (subtle navy) */
outline-color: var(--c-primary); /* selected stroke (accent) */
}
:global([data-mantine-color-scheme="dark"]) .headerResting {
background: #0d1020; /* requested default unselected color */
color: var(--tool-header-text); /* #D0D6DC */
border-bottom-color: var(--tool-header-border); /* #3A4047 */
background: var(--p-navy-950); /* requested default unselected color */
color: var(--c-accent-fg); /* var(--p-gray-300) */
border-bottom-color: var(--c-border); /* #3A4047 */
}
:global([data-mantine-color-scheme="dark"]) .headerSelected {
background: var(--tool-header-border); /* #3A4047 */
color: var(--tool-header-text); /* #D0D6DC */
border-bottom-color: var(--tool-header-border);
}
:global([data-mantine-color-scheme="dark"]) .title {
color: #d0d6dc; /* title text */
}
:global([data-mantine-color-scheme="dark"]) .meta {
color: #6b7280; /* subtitle text */
}
/* Light mode selected header stroke override */
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
outline-color: #3b4b6e;
background: var(--c-border); /* #3A4047 */
color: var(--c-accent-fg); /* var(--p-gray-300) */
border-bottom-color: var(--c-border);
}
/* =========================
@@ -332,8 +319,8 @@
max-width: 260px;
height: calc(310px - 0.5rem);
margin: 0.5rem auto 0;
background: var(--file-card-bg);
border: 1.5px solid var(--border-default);
background: var(--c-bg);
border: 1.5px solid var(--c-border);
border-radius: 12px;
box-shadow: var(--shadow-md);
cursor: pointer;
@@ -347,11 +334,11 @@
.addFileCard:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-1px);
border-color: var(--accent-interactive);
border-color: var(--c-primary);
}
.addFileCard:focus-visible {
outline: 2px solid var(--accent-interactive);
outline: 2px solid var(--c-primary);
outline-offset: 2px;
}
@@ -386,7 +373,7 @@
}
.addFileCard:hover .addFileText {
color: var(--text-primary);
color: var(--c-text);
}
.addFileSubtext {
@@ -75,7 +75,7 @@
box-shadow: var(--shadow-md);
overflow: hidden;
position: relative;
background: var(--bg-surface);
background: var(--c-surface);
transition:
box-shadow 0.15s ease,
transform 0.2s ease;
@@ -91,7 +91,7 @@
height: 100%;
object-fit: contain;
border-radius: 12px;
background: var(--bg-surface);
background: var(--c-surface);
display: block;
position: relative;
z-index: 1;
@@ -115,7 +115,7 @@
.fileName {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
color: var(--c-text);
line-height: var(--file-name-line-height);
height: calc(var(--file-name-lines) * var(--file-name-line-height));
margin: 0;
@@ -149,7 +149,7 @@
.fileMeta {
font-size: 11px;
color: var(--text-secondary);
color: var(--c-text-muted);
line-height: var(--file-meta-line-height);
height: var(--file-meta-line-height);
overflow: hidden;
@@ -183,7 +183,7 @@
display: flex;
align-items: center;
justify-content: center;
background: rgba(220, 38, 38, 0.12);
background: color-mix(in srgb, var(--p-red-600) 12%, transparent);
border-radius: 12px;
z-index: 2;
pointer-events: none;
@@ -268,7 +268,7 @@
bottom: 6px;
right: 6px;
cursor: grab;
color: var(--text-secondary);
color: var(--c-text-muted);
opacity: 0;
transition: opacity 0.15s ease;
pointer-events: none;
@@ -50,7 +50,7 @@ const DesktopLayout: React.FC = () => {
flex: 1,
display: "flex",
flexDirection: "column",
backgroundColor: "var(--bg-file-list)",
backgroundColor: "var(--c-surface)",
border: "1px solid var(--mantine-color-gray-2)",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
overflow: "hidden",
@@ -81,8 +81,8 @@ const EmptyFilesState: React.FC = () => {
variant="tertiary"
aria-label={t("emptyFilesState.upload", "Upload")}
style={{
backgroundColor: "var(--bg-file-manager)",
color: "var(--landing-button-color)",
backgroundColor: "var(--c-bg)",
color: "var(--c-accent-fg)",
border: "1px solid var(--landing-button-border)",
borderRadius: isUploadHover ? "2rem" : "1rem",
height: "38px",
@@ -103,7 +103,7 @@ const EmptyFilesState: React.FC = () => {
icon={icons.uploadIconName}
width="1.25rem"
height="1.25rem"
style={{ color: "var(--accent-interactive)" }}
style={{ color: "var(--c-primary)" }}
/>
{isUploadHover && (
<span style={{ marginLeft: ".5rem" }}>
@@ -115,7 +115,7 @@ const EmptyFilesState: React.FC = () => {
{/* Instruction Text */}
<span
className="text-[var(--accent-interactive)]"
className="text-[var(--c-primary)]"
style={{ fontSize: ".8rem", textAlign: "center" }}
>
{terminology.dropFilesHere}
@@ -189,7 +189,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
backgroundColor:
isSelected || shouldShowHovered
? "var(--mantine-color-gray-1)"
: "var(--bg-file-list)",
: "var(--c-surface)",
opacity: 1,
transition: "background-color 0.15s ease",
userSelect: "none",
@@ -47,7 +47,7 @@ const MobileLayout: React.FC = () => {
flex: 1,
display: "flex",
flexDirection: "column",
backgroundColor: "var(--bg-file-list)",
backgroundColor: "var(--c-surface)",
borderRadius: "0.5rem",
border: "1px solid var(--mantine-color-gray-2)",
overflow: "hidden",
@@ -201,7 +201,7 @@ export function FileDetailsPanel({
<img src={single.thumbnailUrl} alt="" />
) : (
<PictureAsPdfIcon
style={{ fontSize: "3rem", color: "var(--text-muted)" }}
style={{ fontSize: "3rem", color: "var(--c-text-subtle)" }}
/>
)}
</div>
@@ -499,7 +499,7 @@ function FolderCard({
<div
className="files-page-card-thumb"
style={{
background: `linear-gradient(135deg, color-mix(in srgb, ${folder.color ?? "var(--accent-interactive, #6366f1)"} 18%, var(--bg-surface)), color-mix(in srgb, ${folder.color ?? "var(--accent-interactive, #6366f1)"} 6%, var(--bg-surface)))`,
background: `linear-gradient(135deg, color-mix(in srgb, ${folder.color ?? "var(--c-primary, #6366f1)"} 18%, var(--c-surface)), color-mix(in srgb, ${folder.color ?? "var(--c-primary, #6366f1)"} 6%, var(--c-surface)))`,
}}
>
<FolderThumbnail
@@ -845,7 +845,7 @@ export default function FileManagerView() {
fontSize: "0.95rem",
fontWeight: 600,
padding: "0.25rem 0.5rem",
color: "var(--text-primary)",
color: "var(--c-text)",
}}
>
{currentTab === "local"
@@ -992,8 +992,8 @@ export default function FileManagerView() {
padding: "0.6rem 1.25rem",
background:
"color-mix(in srgb, var(--mantine-color-red-6, #e03131) 12%, transparent)",
color: "var(--text-primary)",
borderBottom: "1px solid var(--border-subtle)",
color: "var(--c-text)",
borderBottom: "1px solid var(--c-border-subtle)",
fontSize: "0.85rem",
display: "flex",
justifyContent: "space-between",
@@ -1083,15 +1083,15 @@ export default function FileManagerView() {
style={{
background:
currentTab === tab.id
? "var(--hover-bg)"
? "var(--c-hover)"
: "transparent",
border: "none",
borderRadius: "0.3rem",
padding: "0.2rem 0.6rem",
color:
currentTab === tab.id
? "var(--text-primary)"
: "var(--text-muted)",
? "var(--c-text)"
: "var(--c-text-subtle)",
fontWeight: currentTab === tab.id ? 500 : 400,
fontSize: "0.75rem",
cursor: "pointer",
@@ -1662,7 +1662,7 @@ const SearchField = React.forwardRef<
const { t } = useTranslation();
return (
<div className="files-page-search">
<SearchIcon fontSize="small" style={{ color: "var(--text-muted)" }} />
<SearchIcon fontSize="small" style={{ color: "var(--c-text-subtle)" }} />
<input
ref={ref}
type="text"
@@ -28,13 +28,13 @@ const styles = {
},
local: {
background:
"color-mix(in srgb, var(--text-muted, #6b7280) 16%, transparent)",
color: "var(--text-secondary)",
"color-mix(in srgb, var(--c-text-subtle, #6b7280) 16%, transparent)",
color: "var(--c-text-muted)",
},
cloud: {
background:
"color-mix(in srgb, var(--accent-interactive, #6366f1) 16%, transparent)",
color: "var(--accent-interactive, #6366f1)",
"color-mix(in srgb, var(--c-primary, #6366f1) 16%, transparent)",
color: "var(--c-primary, #6366f1)",
},
shared: {
background:
@@ -3,8 +3,8 @@
flex-direction: column;
width: 100%;
height: 100%;
background: var(--bg-background);
color: var(--text-primary);
background: var(--c-bg);
color: var(--c-text);
overflow: hidden;
}
@@ -22,8 +22,8 @@
gap: 1rem;
min-height: 48px;
padding: 0 0.75rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
border-bottom: 1px solid var(--c-border-subtle);
background: var(--c-bg-raised);
flex-shrink: 0;
}
@@ -52,7 +52,7 @@
border: none;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
color: var(--text-secondary);
color: var(--c-text-muted);
cursor: pointer;
font-size: inherit;
font-weight: 500;
@@ -64,19 +64,14 @@
overflow: hidden;
text-overflow: ellipsis;
}
.files-page-breadcrumb:hover {
background: var(--hover-bg);
color: var(--text-primary);
}
.files-page-breadcrumb:hover,
.files-page-breadcrumb.is-current {
color: var(--text-primary);
background: var(--hover-bg);
background: var(--c-hover);
color: var(--c-text);
}
.files-page-breadcrumb-sep {
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 1rem !important;
width: 1rem;
height: 1rem;
@@ -88,8 +83,8 @@
display: flex;
align-items: center;
gap: 0.35rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 999px;
padding: 0.2rem 0.75rem;
/* Fills its grid cell; the cell's minmax(...) clamps to a sensible range. */
@@ -102,7 +97,7 @@
border: none;
outline: none;
flex: 1;
color: var(--text-primary);
color: var(--c-text);
font-size: 0.9rem;
}
@@ -116,8 +111,8 @@
.files-page-tree {
width: 17rem;
flex-shrink: 0;
border-right: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
border-right: 1px solid var(--c-border-subtle);
background: var(--c-bg-raised);
overflow-y: auto;
display: flex;
flex-direction: column;
@@ -135,7 +130,7 @@
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
color: var(--c-text-subtle);
}
/* Tree node styles moved to FolderTreePanel.css - that's where the
@@ -156,8 +151,8 @@
align-items: center;
gap: 0.75rem;
padding: 0.55rem 1.25rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-background);
border-bottom: 1px solid var(--c-border-subtle);
background: var(--c-bg);
flex-shrink: 0;
flex-wrap: wrap;
min-height: 3rem;
@@ -165,7 +160,7 @@
.files-page-toolbar-info {
font-size: 0.85rem;
color: var(--text-secondary);
color: var(--c-text-muted);
font-weight: 500;
}
@@ -178,7 +173,7 @@
}
.files-page-toolbar-info span {
color: var(--text-muted);
color: var(--c-text-subtle);
}
.files-page-toolbar-actions {
@@ -226,7 +221,7 @@
.files-page-toolbar-divider {
width: 1px;
height: 1.4rem;
background: var(--border-subtle);
background: var(--c-border-subtle);
margin: 0 0.15rem;
}
@@ -264,7 +259,7 @@
padding: 1.25rem;
position: relative;
scrollbar-width: thin;
scrollbar-color: var(--border-subtle) transparent;
scrollbar-color: var(--c-border-subtle) transparent;
}
.files-page-content::-webkit-scrollbar {
@@ -273,14 +268,14 @@
}
.files-page-content::-webkit-scrollbar-thumb {
background: var(--border-subtle);
background: var(--c-border-subtle);
border-radius: 999px;
border: 2px solid transparent;
background-clip: content-box;
}
.files-page-content::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
background: var(--c-text-subtle);
background-clip: content-box;
}
@@ -288,7 +283,7 @@
.folder-tree-panel-inner,
.files-page-details-body {
scrollbar-width: thin;
scrollbar-color: var(--border-subtle) transparent;
scrollbar-color: var(--c-border-subtle) transparent;
}
.files-page-tree-list::-webkit-scrollbar,
@@ -300,7 +295,7 @@
.files-page-tree-list::-webkit-scrollbar-thumb,
.folder-tree-panel-inner::-webkit-scrollbar-thumb,
.files-page-details-body::-webkit-scrollbar-thumb {
background: var(--border-subtle);
background: var(--c-border-subtle);
border-radius: 999px;
}
@@ -313,10 +308,10 @@
.files-page-list {
display: flex;
flex-direction: column;
border: 1px solid var(--border-subtle);
border: 1px solid var(--c-border-subtle);
border-radius: 0.6rem;
overflow: hidden;
background: var(--bg-surface);
background: var(--c-surface);
}
.files-page-list-row {
@@ -325,7 +320,7 @@
gap: 0.5rem;
align-items: center;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--c-border-subtle);
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.12s ease;
@@ -336,8 +331,8 @@
}
.files-page-list-row.is-header {
background: var(--bg-toolbar);
color: var(--text-muted);
background: var(--c-bg-raised);
color: var(--c-text-subtle);
text-transform: uppercase;
font-size: 0.72rem;
letter-spacing: 0.05em;
@@ -356,33 +351,25 @@
}
.files-page-list-row.is-header [data-sortable="true"]:hover {
background: var(--hover-bg);
color: var(--text-primary);
background: var(--c-hover);
color: var(--c-text);
}
.files-page-list-row:not(.is-header):hover {
background: var(--hover-bg);
}
.files-page-list-row:not(.is-header):hover,
.files-page-list-row.is-selected {
background: var(--hover-bg);
background: var(--c-hover);
}
.files-page-list-row.is-drop-target {
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 12%,
transparent
);
box-shadow: inset 0 0 0 1px var(--accent-interactive, #6366f1);
background: color-mix(in srgb, var(--c-primary) 12%, transparent);
box-shadow: inset 0 0 0 1px var(--c-primary);
}
.files-page-card {
position: relative;
display: flex;
flex-direction: column;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.85rem;
overflow: hidden;
cursor: pointer;
@@ -397,8 +384,8 @@
transform: translateY(-2px);
border-color: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 35%,
var(--border-subtle)
var(--c-primary) 35%,
var(--c-border-subtle)
);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.08),
@@ -406,16 +393,15 @@
}
.files-page-card:focus-visible {
outline: 2px solid var(--accent-interactive, #6366f1);
outline: 2px solid var(--c-primary);
outline-offset: 2px;
}
.files-page-card.is-selected {
border-color: var(--accent-interactive, #6366f1);
border-color: var(--c-primary);
box-shadow:
0 0 0 2px var(--accent-interactive, #6366f1),
0 6px 18px -6px
color-mix(in srgb, var(--accent-interactive, #6366f1) 35%, transparent);
0 0 0 2px var(--c-primary),
0 6px 18px -6px color-mix(in srgb, var(--c-primary) 35%, transparent);
}
.files-page-card.is-folder {
@@ -426,8 +412,8 @@
aspect-ratio: 4 / 3;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--text-muted) 5%, var(--bg-surface)),
var(--bg-surface)
color-mix(in srgb, var(--c-text-subtle) 5%, var(--c-surface)),
var(--c-surface)
);
display: flex;
align-items: center;
@@ -452,7 +438,7 @@
flex-direction: column;
align-items: center;
gap: 0.4rem;
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.04em;
@@ -472,13 +458,13 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
color: var(--c-text);
letter-spacing: -0.005em;
}
.files-page-card-meta {
font-size: 0.76rem;
color: var(--text-muted);
color: var(--c-text-subtle);
display: flex;
align-items: center;
gap: 0.4rem;
@@ -488,7 +474,7 @@
the user can tell which folder each hit lives in without navigating. */
.files-page-card-path {
font-size: 0.72rem;
color: var(--text-muted);
color: var(--c-text-subtle);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -517,8 +503,8 @@
/* Style the Mantine checkbox so it reads as a soft pill rather than a
bare form control. */
.files-page-card-selector :where(.mantine-Checkbox-input) {
background: var(--bg-surface);
border-color: var(--border-subtle);
background: var(--c-surface);
border-color: var(--c-border-subtle);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}
@@ -536,26 +522,26 @@
Use stronger specificity + !important to win over Mantine's
variant=filled inline styles. */
.files-page-card .files-page-card-actions button.mantine-ActionIcon-root {
background-color: var(--bg-raised, var(--bg-surface)) !important;
color: var(--text-primary) !important;
border: 1px solid var(--border-default, var(--border-subtle)) !important;
background-color: var(--c-surface-raised, var(--c-surface)) !important;
color: var(--c-text) !important;
border: 1px solid var(--c-border, var(--c-border-subtle)) !important;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(6px);
}
.files-page-card .files-page-card-actions button.mantine-ActionIcon-root:hover {
background-color: var(--hover-bg) !important;
background-color: var(--c-hover) !important;
}
/* Row-view kebab: same high-contrast treatment so it's readable
against selected (tinted) and in-workspace rows. */
.files-page-list-row .mantine-ActionIcon-root[aria-label$="actions"] {
color: var(--text-secondary);
color: var(--c-text-muted);
}
.files-page-list-row .mantine-ActionIcon-root[aria-label$="actions"]:hover {
background-color: var(--hover-bg) !important;
color: var(--text-primary);
background-color: var(--c-hover) !important;
color: var(--c-text);
}
.files-page-card-origin {
@@ -578,7 +564,7 @@
gap: 0.35rem;
padding: 0.22rem 0.55rem;
border-radius: 999px;
background: var(--mantine-color-teal-6, #10b981);
background: var(--mantine-color-teal-6, var(--p-emerald-500));
color: #ffffff;
font-size: 0.68rem;
font-weight: 700;
@@ -614,8 +600,8 @@
.files-page-card.is-in-workspace {
border-color: color-mix(
in srgb,
var(--mantine-color-teal-6, #10b981) 35%,
var(--border-subtle)
var(--mantine-color-teal-6, var(--p-emerald-500)) 35%,
var(--c-border-subtle)
);
}
@@ -627,7 +613,12 @@
pointer-events: none;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--mantine-color-teal-6, #10b981) 6%, transparent) 0%,
color-mix(
in srgb,
var(--mantine-color-teal-6, var(--p-emerald-500)) 6%,
transparent
)
0%,
transparent 40%
);
z-index: 0;
@@ -639,7 +630,7 @@
gap: 0.3rem;
padding: 0.12rem 0.5rem;
border-radius: 999px;
background: var(--mantine-color-teal-6, #10b981);
background: var(--mantine-color-teal-6, var(--p-emerald-500));
color: #ffffff;
font-size: 0.68rem;
font-weight: 700;
@@ -653,7 +644,7 @@
.files-page-list-row.is-in-workspace {
background: color-mix(
in srgb,
var(--mantine-color-teal-6, #10b981) 4%,
var(--mantine-color-teal-6, var(--p-emerald-500)) 4%,
transparent
);
}
@@ -685,13 +676,9 @@
}
.files-page-card.is-drop-target {
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 10%,
var(--bg-surface)
);
box-shadow: 0 0 0 2px var(--accent-interactive, #6366f1);
border-color: var(--accent-interactive, #6366f1);
background: color-mix(in srgb, var(--c-primary) 10%, var(--c-surface));
box-shadow: 0 0 0 2px var(--c-primary);
border-color: var(--c-primary);
}
@keyframes files-page-skeleton-pulse {
@@ -707,9 +694,9 @@
display: block;
background: linear-gradient(
90deg,
color-mix(in srgb, var(--text-muted) 12%, transparent) 25%,
color-mix(in srgb, var(--text-muted) 22%, transparent) 50%,
color-mix(in srgb, var(--text-muted) 12%, transparent) 75%
color-mix(in srgb, var(--c-text-subtle) 12%, transparent) 25%,
color-mix(in srgb, var(--c-text-subtle) 22%, transparent) 50%,
color-mix(in srgb, var(--c-text-subtle) 12%, transparent) 75%
);
background-size: 200% 100%;
animation: files-page-skeleton-pulse 1.4s ease-in-out infinite;
@@ -718,14 +705,14 @@
.files-page-skeleton-card {
pointer-events: none;
border-color: var(--border-subtle);
border-color: var(--c-border-subtle);
box-shadow: none;
}
.files-page-skeleton-card:hover {
transform: none;
box-shadow: none;
border-color: var(--border-subtle);
border-color: var(--c-border-subtle);
}
.files-page-skeleton-row {
@@ -743,7 +730,7 @@
justify-content: center;
height: 100%;
gap: 0.75rem;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 4rem 1.5rem;
text-align: center;
}
@@ -755,25 +742,21 @@
width: 5rem;
height: 5rem;
border-radius: 50%;
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 12%,
transparent
);
color: var(--accent-interactive, #6366f1);
background: color-mix(in srgb, var(--c-primary) 12%, transparent);
color: var(--c-primary);
margin-bottom: 0.25rem;
}
.files-page-empty-title {
font-size: 1.15rem;
color: var(--text-primary);
color: var(--c-text);
font-weight: 600;
letter-spacing: -0.01em;
}
.files-page-empty-hint {
font-size: 0.9rem;
color: var(--text-muted);
color: var(--c-text-subtle);
max-width: 30rem;
line-height: 1.5;
}
@@ -797,8 +780,8 @@
.files-page-details {
width: 22rem;
flex-shrink: 0;
border-left: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
border-left: 1px solid var(--c-border-subtle);
background: var(--c-bg-raised);
display: flex;
flex-direction: column;
overflow: hidden;
@@ -817,7 +800,7 @@
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.files-page-details-header strong {
@@ -846,8 +829,8 @@
flex-direction: column;
gap: 0.4rem;
padding: 0.6rem 1.1rem;
border-top: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
border-top: 1px solid var(--c-border-subtle);
background: var(--c-bg-raised);
}
/* Collapsible "File info" header (size/type/dates). */
@@ -857,11 +840,11 @@
justify-content: space-between;
width: 100%;
padding: 0.4rem 0.6rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.5rem;
cursor: pointer;
color: var(--text-secondary, var(--text-primary));
color: var(--c-text-muted, var(--c-text));
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.04em;
@@ -878,8 +861,8 @@
/* Compact fixed height (was a tall 4:3 box that dominated the panel and
pushed the action buttons off the bottom). */
height: 6.5rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.6rem;
display: flex;
align-items: center;
@@ -911,8 +894,8 @@
gap: 0.55rem;
font-size: 0.85rem;
padding: 0.65rem 0.75rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.5rem;
}
@@ -923,12 +906,12 @@
}
.files-page-details-field-label {
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 0.78rem;
}
.files-page-details-field-value {
color: var(--text-primary);
color: var(--c-text);
text-align: right;
word-break: break-word;
font-weight: 500;
@@ -939,8 +922,8 @@
flex-direction: column;
gap: 0.3rem;
padding: 0.5rem 0.6rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.5rem;
}
@@ -948,7 +931,7 @@
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
color: var(--c-text-subtle);
}
/* Filename chip next to the title - "PDF", "DOCX", etc.
@@ -959,9 +942,9 @@
align-items: center;
padding: 0.12rem 0.5rem;
border-radius: 0.4rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
color: var(--text-secondary, var(--text-primary));
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
color: var(--c-text-muted, var(--c-text));
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.05em;
@@ -980,8 +963,8 @@
flex-direction: column;
gap: 0.4rem;
padding: 0.55rem 0.7rem 0.7rem;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 0.5rem;
}
.files-page-details-version-timeline-label {
@@ -991,12 +974,12 @@
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.files-page-details-version-timeline-count {
margin-left: auto;
font-weight: 600;
color: var(--text-secondary, var(--text-muted));
color: var(--c-text-muted, var(--c-text-subtle));
text-transform: none;
letter-spacing: 0;
}
@@ -1026,27 +1009,26 @@
width: 0.55rem;
height: 0.55rem;
border-radius: 50%;
background: var(--bg-toolbar);
border: 2px solid var(--border-strong, var(--border-subtle));
background: var(--c-bg-raised);
border: 2px solid var(--c-border-strong, var(--c-border-subtle));
z-index: 1;
flex-shrink: 0;
}
.files-page-details-version-timeline-rail-dot.is-active {
background: var(--accent-interactive, #6366f1);
border-color: var(--accent-interactive, #6366f1);
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--accent-interactive, #6366f1) 25%, transparent);
background: var(--c-primary);
border-color: var(--c-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 25%, transparent);
}
.files-page-details-version-timeline-rail-dot.is-ellipsis {
width: 0.35rem;
height: 0.35rem;
background: var(--text-muted);
background: var(--c-text-subtle);
border-color: transparent;
}
.files-page-details-version-timeline-rail-line {
width: 2px;
flex: 1;
background: var(--border-subtle);
background: var(--c-border-subtle);
min-height: 0.6rem;
margin-top: 2px;
}
@@ -1062,11 +1044,7 @@
}
.files-page-details-version-timeline-row.is-active
.files-page-details-version-timeline-body {
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 10%,
transparent
);
background: color-mix(in srgb, var(--c-primary) 10%, transparent);
}
.files-page-details-version-timeline-summary {
appearance: none;
@@ -1084,11 +1062,11 @@
}
.files-page-details-version-timeline-summary:hover
.files-page-details-version-timeline-chevron {
color: var(--text-primary);
color: var(--c-text);
}
.files-page-details-version-timeline-delta {
font-size: 0.82rem;
color: var(--text-primary);
color: var(--c-text);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
@@ -1099,30 +1077,30 @@
}
.files-page-details-version-timeline-delta.is-origin {
font-weight: 400;
color: var(--text-muted);
color: var(--c-text-subtle);
font-style: italic;
}
.files-page-details-version-timeline-delta-plus {
color: var(--accent-interactive, #6366f1);
color: var(--c-primary);
font-weight: 700;
}
.files-page-details-version-timeline-spacer {
flex: 1;
}
.files-page-details-version-timeline-chevron {
color: var(--text-muted);
color: var(--c-text-subtle);
transition: transform 0.15s ease;
}
.files-page-details-version-timeline-chevron.is-expanded {
transform: rotate(180deg);
color: var(--text-primary);
color: var(--c-text);
}
.files-page-details-version-timeline-meta-line {
display: flex;
align-items: center;
gap: 0.35rem;
font-size: 0.7rem;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.files-page-details-version-timeline-expanded {
display: flex;
@@ -1130,7 +1108,7 @@
gap: 0.4rem;
margin-top: 0.45rem;
padding-top: 0.5rem;
border-top: 1px dashed var(--border-subtle);
border-top: 1px dashed var(--c-border-subtle);
}
.files-page-details-version-timeline-toolchain {
display: flex;
@@ -1139,7 +1117,7 @@
}
.files-page-details-version-timeline-toolchain-label {
font-size: 0.65rem;
color: var(--text-muted);
color: var(--c-text-subtle);
text-transform: uppercase;
letter-spacing: 0.05em;
}
@@ -1147,12 +1125,12 @@
.files-page-details-version-timeline-collapse-btn {
appearance: none;
background: none;
border: 1px dashed var(--border-subtle);
border: 1px dashed var(--c-border-subtle);
border-radius: 0.3rem;
padding: 0.25rem 0.5rem;
margin: 0.1rem 0;
font-size: 0.72rem;
color: var(--text-muted);
color: var(--c-text-subtle);
cursor: pointer;
text-align: left;
transition:
@@ -1161,8 +1139,8 @@
}
.files-page-details-version-timeline-ellipsis-btn:hover,
.files-page-details-version-timeline-collapse-btn:hover {
color: var(--text-primary);
border-color: var(--border-strong, var(--text-muted));
color: var(--c-text);
border-color: var(--c-border-strong, var(--c-text-subtle));
}
.files-page-details-version-timeline-collapse-btn {
align-self: flex-start;
@@ -1172,12 +1150,8 @@
.files-page-drop-overlay {
position: absolute;
inset: 1rem;
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 14%,
var(--bg-background)
);
border: 2px dashed var(--accent-interactive, #6366f1);
background: color-mix(in srgb, var(--c-primary) 14%, var(--c-bg));
border: 2px dashed var(--c-primary);
border-radius: 1rem;
display: flex;
flex-direction: column;
@@ -1185,7 +1159,7 @@
justify-content: center;
gap: 1rem;
pointer-events: none;
color: var(--accent-interactive, #6366f1);
color: var(--c-primary);
font-weight: 600;
font-size: 1.1rem;
z-index: 10;
@@ -1200,40 +1174,28 @@
width: 4.5rem;
height: 4.5rem;
border-radius: 50%;
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 30%,
transparent
);
background: color-mix(in srgb, var(--c-primary) 30%, transparent);
}
.files-page-drop-overlay-icon svg {
font-size: 2.5rem !important;
color: var(--bg-background);
color: var(--c-bg);
}
.files-page-drop-overlay-sub {
font-size: 0.85rem;
font-weight: 500;
color: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 80%,
var(--text-secondary)
);
color: color-mix(in srgb, var(--c-primary) 80%, var(--c-text-muted));
}
@keyframes files-page-drop-overlay-pulse {
0%,
100% {
border-color: var(--accent-interactive, #6366f1);
border-color: var(--c-primary);
transform: scale(1);
}
50% {
border-color: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 60%,
transparent
);
border-color: color-mix(in srgb, var(--c-primary) 60%, transparent);
transform: scale(1.005);
}
}
@@ -70,7 +70,7 @@ export function FolderAppearancePicker({
borderRadius: "50%",
border:
folder.color === c
? "2px solid var(--text-primary)"
? "2px solid var(--c-text)"
: "2px solid transparent",
background: c,
cursor: disabled ? "not-allowed" : "pointer",
@@ -123,7 +123,7 @@ function Section({
fontSize: "0.7rem",
textTransform: "uppercase",
letterSpacing: "0.05em",
color: "var(--text-muted)",
color: "var(--c-text-subtle)",
fontWeight: 600,
}}
>
@@ -163,13 +163,13 @@ function IconButton({
justifyContent: "center",
fontSize: "1.1rem",
borderRadius: "0.4rem",
background: selected ? "var(--hover-bg)" : "transparent",
background: selected ? "var(--c-hover)" : "transparent",
border: selected
? "1px solid var(--accent-interactive, #6366f1)"
? "1px solid var(--c-primary, #6366f1)"
: "1px solid transparent",
cursor: disabled ? "not-allowed" : "pointer",
padding: 0,
color: "var(--text-primary)",
color: "var(--c-text)",
}}
>
{icon.glyph || "-"}
@@ -30,7 +30,7 @@ export function FolderThumbnail({
size = "thumb",
iconGlyph,
}: FolderThumbnailProps) {
const accent = color ?? "var(--accent-interactive, #6366f1)";
const accent = color ?? "var(--c-primary, #6366f1)";
const px = SIZE_PX[size];
const showBadge = size === "thumb" && (fileCount ?? 0) > 0;
// Per-instance unique ids - `${color}` previously embedded `#` and CSS
@@ -149,7 +149,7 @@ export function FolderThumbnail({
minWidth: "1.4rem",
height: "1.4rem",
borderRadius: "999px",
background: "var(--bg-surface, #fff)",
background: "var(--c-surface, #fff)",
border: `1px solid ${accent}`,
color: accent,
fontSize: "0.7rem",
@@ -15,8 +15,8 @@
.folder-tree-panel {
width: 0;
flex-shrink: 0;
background: var(--bg-toolbar);
border-right: 0 solid var(--border-subtle);
background: var(--c-bg-raised);
border-right: 0 solid var(--c-border-subtle);
overflow: hidden;
height: 100%;
pointer-events: none;
@@ -65,11 +65,7 @@
.folder-tree-panel-resizer:hover,
.folder-tree-panel-resizer:focus-visible {
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 35%,
transparent
);
background: color-mix(in srgb, var(--c-primary) 35%, transparent);
outline: none;
}
@@ -87,7 +83,7 @@
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--text-muted);
color: var(--c-text-subtle);
text-transform: uppercase;
}
@@ -112,7 +108,7 @@
margin: 0 8px;
cursor: pointer;
user-select: none;
color: var(--text-secondary);
color: var(--c-text-muted);
font-size: 14px;
position: relative;
transition: background-color 0.15s ease;
@@ -120,23 +116,19 @@
}
.files-page-tree-node:hover {
background: var(--hover-bg);
background: var(--c-hover);
}
.files-page-tree-node.is-active {
background: var(--hover-bg);
color: var(--text-primary);
background: var(--c-hover);
color: var(--c-text);
font-weight: 500;
}
.files-page-tree-node.is-drop-target {
background: color-mix(
in srgb,
var(--accent-interactive, #6366f1) 12%,
transparent
);
box-shadow: inset 0 0 0 1px var(--accent-interactive, #6366f1);
color: var(--text-primary);
background: color-mix(in srgb, var(--c-primary) 12%, transparent);
box-shadow: inset 0 0 0 1px var(--c-primary);
color: var(--c-text);
}
.files-page-tree-toggle {
@@ -145,7 +137,7 @@
justify-content: center;
width: 16px;
height: 16px;
color: var(--text-muted);
color: var(--c-text-subtle);
flex-shrink: 0;
}
@@ -165,7 +157,7 @@
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-left: 8px;
font-size: 18px;
}
@@ -183,7 +175,7 @@
}
.files-page-tree-count {
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 12px;
flex-shrink: 0;
margin-left: 8px;
@@ -127,7 +127,7 @@ export function MoveToFolderDialog({
</Text>
<div
style={{
border: "1px solid var(--border-subtle)",
border: "1px solid var(--c-border-subtle)",
borderRadius: "0.5rem",
maxHeight: "20rem",
overflowY: "auto",
@@ -343,8 +343,8 @@ function FolderPick({
opacity: disabled ? 0.45 : 1,
gap: "0.5rem",
padding: `0.4rem 0.75rem 0.4rem ${0.75 + depth * 0.85}rem`,
background: isActive ? "var(--hover-bg)" : "transparent",
borderBottom: "1px solid var(--border-subtle)",
background: isActive ? "var(--c-hover)" : "transparent",
borderBottom: "1px solid var(--c-border-subtle)",
boxSizing: "border-box",
fontWeight: isActive ? 600 : 400,
}}
@@ -218,7 +218,7 @@ export default function Workbench() {
<Box
className="flex-1 h-full min-w-0 relative flex flex-col"
data-tour="workbench"
style={{ backgroundColor: "var(--bg-background)", minWidth: 0 }}
style={{ backgroundColor: "var(--c-bg)", minWidth: 0 }}
>
{/* Workbench Bar - animates in/out based on file presence */}
{currentView !== "myFiles" &&
@@ -22,7 +22,7 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.18);
box-shadow: 0 16px 32px color-mix(in srgb, var(--p-gray-900) 18%, transparent);
animation: heroLogoScale 0.25s ease forwards;
}
@@ -49,10 +49,10 @@
.securityCard {
width: 100%;
max-width: 480px;
background: var(--bg-surface, #ffffff);
background: var(--c-surface, #ffffff);
border-radius: 16px;
padding: 20px;
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12);
box-shadow: 0 12px 32px color-mix(in srgb, var(--p-gray-900) 12%, transparent);
display: flex;
flex-direction: column;
gap: 16px;
@@ -65,7 +65,7 @@
font-weight: 500;
font-size: 15px;
line-height: 1.5;
color: var(--onboarding-body, #1f2937);
color: var(--c-text-muted, var(--p-gray-800));
}
.mfaSlideContent {
@@ -77,10 +77,10 @@
.mfaCard {
width: 100%;
max-width: 560px;
background: var(--bg-surface, #ffffff);
background: var(--c-surface, #ffffff);
border-radius: 16px;
padding: 20px;
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12);
box-shadow: 0 12px 32px color-mix(in srgb, var(--p-gray-900) 12%, transparent);
}
.mfaSetupGrid {
@@ -94,7 +94,7 @@
padding: 12px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(15, 23, 42, 0.12);
box-shadow: 0 8px 16px color-mix(in srgb, var(--p-gray-900) 12%, transparent);
display: flex;
justify-content: center;
}
@@ -102,7 +102,7 @@
.mfaSteps {
margin: 0;
padding-left: 18px;
color: var(--onboarding-body, #1f2937);
color: var(--c-text-muted, var(--p-gray-800));
font-size: 14px;
line-height: 1.5;
}
@@ -149,11 +149,7 @@
padding: 4px;
outline: none;
}
.iconButton:focus {
outline: none;
}
.iconButton:focus,
.iconButton:focus-visible {
outline: none;
}
@@ -194,14 +190,7 @@
text-align: center;
animation: heroLogoEnter 0.25s ease forwards;
}
.title {
text-align: center;
opacity: 0;
transform: translateX(24px);
animation: bodySlideIn 0.25s ease forwards;
}
.title,
.bodyCopy {
text-align: center;
opacity: 0;
@@ -273,13 +262,13 @@
opacity: 1;
border: 1px solid rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.9);
color: #1f2933;
color: var(--p-gray-800);
box-shadow: 0 0 8px rgba(255, 255, 255, 0.7);
}
/* Modal content styles */
.modalContent {
background: var(--bg-surface);
background: var(--c-surface);
position: relative;
}
@@ -299,7 +288,7 @@
sans-serif;
font-weight: 600;
font-size: 22px;
color: var(--onboarding-title);
color: var(--c-text);
}
/* Body text styles */
@@ -313,7 +302,7 @@
Arial,
sans-serif;
font-size: 16px;
color: var(--onboarding-body);
color: var(--c-text-muted);
line-height: 1.5;
}
@@ -335,8 +324,8 @@
}
.v2Badge {
background: #dbefff;
color: #2a4bff;
background: var(--c-primary-tint);
color: var(--c-accent-fg);
padding: 3px 9px;
border-radius: 6px;
font-size: 12px;
@@ -345,7 +334,7 @@
/* Icon styles */
.heroIcon {
color: var(--onboarding-title, #0f172a);
color: var(--c-text, var(--p-gray-900));
}
/* ===================================================================== */
@@ -353,7 +342,7 @@
/* ===================================================================== */
.card {
background: var(--bg-surface);
background: var(--c-surface);
display: flex;
flex-direction: column;
}
@@ -383,7 +372,7 @@
font-size: 17px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--onboarding-title, #0f172a);
color: var(--c-text, var(--p-gray-900));
}
.headerRight {
@@ -395,8 +384,8 @@
.stepPill {
font-size: 12px;
font-weight: 600;
color: var(--onboarding-body, #64748b);
background: var(--bg-muted, #f1f5f9);
color: var(--c-text-muted, var(--p-gray-500));
background: var(--c-surface-sunken, var(--p-gray-100));
padding: 5px 11px;
border-radius: 9999px;
white-space: nowrap;
@@ -413,17 +402,20 @@
flex: 1;
height: 5px;
border-radius: 9999px;
background: var(--onboarding-step-inactive, #e2e8f0);
background: var(--onboarding-step-inactive, var(--p-gray-250));
transition: background 0.2s ease;
}
.progressSegDone {
background: var(--color-blue, #3b82f6);
background: var(--c-primary, var(--p-blue-500));
}
.divider {
height: 1px;
background: var(--border-subtle, rgba(15, 23, 42, 0.08));
background: var(
--c-border-subtle,
color-mix(in srgb, var(--p-gray-900) 8%, transparent)
);
}
/* ---- content ---- */
@@ -445,7 +437,12 @@
min-height: 128px;
border-radius: 14px;
overflow: hidden;
background: linear-gradient(155deg, #eef1fb 0%, #f6f4fc 55%, #fbf4f7 100%);
background: linear-gradient(
155deg,
var(--p-tint-blue) 0%,
var(--p-tint-violet) 55%,
var(--p-tint-pink) 100%
);
}
.heroArt {
@@ -459,7 +456,9 @@
height: 62px;
border-radius: 15px;
object-fit: contain;
filter: drop-shadow(0 10px 20px rgba(15, 23, 42, 0.16));
filter: drop-shadow(
0 10px 20px color-mix(in srgb, var(--p-gray-900) 16%, transparent)
);
}
.heroTile {
@@ -470,7 +469,7 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10px 22px rgba(15, 23, 42, 0.14);
box-shadow: 0 10px 22px color-mix(in srgb, var(--p-gray-900) 14%, transparent);
}
/* ---- title + body (left-aligned, clean) ---- */
@@ -479,14 +478,14 @@
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.3;
color: var(--onboarding-title, #0f172a);
color: var(--c-text, var(--p-gray-900));
text-align: left;
}
.bodyNew {
font-size: 14px;
line-height: 1.5;
color: var(--onboarding-body, #475569);
color: var(--c-text-muted, var(--p-gray-600));
text-align: left;
}
@@ -515,14 +514,15 @@
/* ---- dark theme surface tuning ---- */
:global([data-mantine-color-scheme="dark"]) .heroPanel {
background: linear-gradient(155deg, #1a2236 0%, #171e30 55%, #201a28 100%);
background: linear-gradient(
155deg,
var(--p-navy-850) 0%,
var(--p-navy-880) 55%,
var(--p-plum-900) 100%
);
}
:global([data-mantine-color-scheme="dark"]) .heroTile {
background: #0f1626;
background: var(--p-navy-900);
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);
}
:global([data-mantine-color-scheme="dark"]) .divider {
background: rgba(255, 255, 255, 0.08);
}
@@ -116,7 +116,7 @@ export default function OnboardingSlideShell({
content: {
overflow: "hidden",
border: "none",
background: "var(--bg-surface)",
background: "var(--c-surface)",
maxHeight: "90vh",
},
}}
@@ -193,7 +193,7 @@ export default function OnboardingSlideShell({
<div key={`body-${slideKey}`} className={styles.bodyNew}>
{body}
<style>{`.${styles.bodyNew} strong{color: var(--onboarding-title); font-weight: 600;}`}</style>
<style>{`.${styles.bodyNew} strong{color: var(--c-text); font-weight: 600;}`}</style>
</div>
<div className={styles.footer}>
@@ -13,7 +13,7 @@
box-shadow:
0 0 0 2px var(--mantine-primary-color-filled),
0 0 15px var(--mantine-primary-color-filled),
inset 0 0 15px rgba(59, 130, 246, 0.1);
inset 0 0 15px color-mix(in srgb, var(--p-blue-500) 10%, transparent);
border-radius: 8px;
}
@@ -23,13 +23,13 @@
box-shadow:
0 0 0 3px var(--mantine-primary-color-filled),
0 0 20px var(--mantine-primary-color-filled),
inset 0 0 20px rgba(59, 130, 246, 0.1);
inset 0 0 20px color-mix(in srgb, var(--p-blue-500) 10%, transparent);
}
50% {
box-shadow:
0 0 0 3px var(--mantine-primary-color-filled),
0 0 30px var(--mantine-primary-color-filled),
inset 0 0 30px rgba(59, 130, 246, 0.2);
inset 0 0 30px color-mix(in srgb, var(--p-blue-500) 20%, transparent);
}
}
@@ -40,7 +40,7 @@
position: absolute;
border-radius: 50%;
pointer-events: none;
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.12);
box-shadow: 0 18px 36px color-mix(in srgb, var(--p-gray-900) 12%, transparent);
animation-name: circleSway;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
@@ -129,9 +129,9 @@ export const DesktopInstallTitle: React.FC<DesktopInstallTitleProps> = ({
leftSection={iconKey ? <OsIcon os={iconKey} /> : undefined}
style={{
backgroundColor: isSelected
? "var(--bg-muted, #f1f5f9)"
? "var(--c-surface-sunken, #f1f5f9)"
: "transparent",
color: "var(--onboarding-title, #0f172a)",
color: "var(--c-text, #0f172a)",
fontWeight: isSelected ? 600 : 500,
}}
>
@@ -125,7 +125,7 @@ function FirstLoginForm({
icon="info-rounded"
width={20}
height={20}
style={{ color: "#3B82F6", flexShrink: 0 }}
style={{ color: "var(--p-blue-500)", flexShrink: 0 }}
/>
<span>
{t(
@@ -26,7 +26,7 @@ export default function SecurityCheckSlide({
icon="error"
width={20}
height={20}
style={{ color: "#F04438", flexShrink: 0 }}
style={{ color: "var(--p-red-500)", flexShrink: 0 }}
/>
<span>
{i18n.t(
@@ -28,15 +28,15 @@
.selectionBox {
position: absolute;
border: 2px dashed #3b82f6;
background-color: rgba(59, 130, 246, 0.1);
border: 2px dashed var(--c-primary);
background-color: color-mix(in srgb, var(--c-primary) 10%, transparent);
pointer-events: none;
}
.dropIndicator {
position: absolute;
width: 4px;
background-color: rgba(96, 165, 250, 0.8);
background-color: color-mix(in srgb, var(--c-primary) 80%, transparent);
border-radius: 2px;
pointer-events: none;
}
@@ -50,8 +50,8 @@
position: absolute;
top: -8px;
right: -8px;
background-color: #3b82f6;
color: #ffffff;
background-color: var(--c-primary);
color: var(--c-text-on-primary);
border-radius: 50%;
width: 32px;
height: 32px;
@@ -241,7 +241,7 @@ const FileThumbnail = ({
<CheckboxIndicator
checked={isSelected}
onChange={() => onToggleFile(file.id)}
color="var(--checkbox-checked-bg)"
color="var(--c-primary)"
/>
) : (
<div className={styles.unsupportedPill}>
@@ -372,7 +372,7 @@ const FileThumbnail = ({
objectFit: "contain",
borderRadius: 0,
background: "#ffffff",
border: "1px solid var(--border-default)",
border: "1px solid var(--c-border)",
display: "block",
marginLeft: "auto",
marginRight: "auto",
@@ -21,13 +21,13 @@
@keyframes pageMovedHighlight {
0% {
background-color: rgba(59, 130, 246, 0.32);
background-color: color-mix(in srgb, var(--c-primary) 32%, transparent);
}
60% {
background-color: rgba(59, 130, 246, 0.12);
background-color: color-mix(in srgb, var(--c-primary) 12%, transparent);
}
100% {
background-color: rgba(59, 130, 246, 0);
background-color: color-mix(in srgb, var(--c-primary) 0%, transparent);
}
}
@@ -63,7 +63,7 @@
/* Action styles */
.actionRow:hover {
background: var(--hover-bg);
background: var(--c-hover);
}
.actionDanger {
@@ -72,7 +72,7 @@
.actionsDivider {
height: 1px;
background: var(--border-default);
background: var(--c-border);
margin: 4px 0;
}
@@ -86,7 +86,7 @@
.unsupportedPill {
margin-left: 1.75rem;
background: #6b7280;
background: var(--p-gray-500);
color: white;
padding: 4px 8px;
border-radius: 12px;
@@ -121,8 +121,8 @@ const PageEditorControls = ({
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
backgroundColor: "var(--bg-toolbar)",
border: "1px solid var(--border-default)",
backgroundColor: "var(--c-bg-raised)",
border: "1px solid var(--c-border)",
borderRadius: "16px 16px 0 0",
pointerEvents: "auto",
minWidth: 360,
@@ -31,7 +31,7 @@
.rightCol {
width: 8rem;
border-left: 0.0625rem solid var(--border-default);
border-left: 0.0625rem solid var(--c-border);
padding-left: 0.75rem;
display: flex;
flex-direction: column;
@@ -47,15 +47,15 @@
width: 100%;
border-radius: 1.25rem;
border: 0.0625rem solid var(--bulk-card-border);
background-color: var(--bulk-card-bg);
color: var(--text-primary);
background-color: var(--c-surface);
color: var(--c-text);
transition: all 0.2s ease;
min-height: 2rem;
}
.operatorChip:hover:not(:disabled) {
border-color: var(--bulk-card-hover-border);
background-color: var(--hover-bg);
background-color: var(--c-hover);
transform: translateY(-0.0625rem);
box-shadow: var(--shadow-sm);
}
@@ -70,24 +70,12 @@
cursor: not-allowed;
}
:global([data-mantine-color-scheme="dark"]) .operatorChip {
background-color: var(--bulk-card-bg);
border-color: var(--bulk-card-border);
color: var(--text-primary);
}
:global([data-mantine-color-scheme="dark"]) .operatorChip:hover:not(:disabled) {
background-color: var(--hover-bg);
border-color: var(--bulk-card-hover-border);
color: var(--text-primary);
}
.dropdownHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-bottom: 0.0625rem solid var(--border-default);
border-bottom: 0.0625rem solid var(--c-border);
margin-bottom: 0.5rem;
}
@@ -111,7 +99,7 @@
}
.chevron {
color: var(--text-muted);
color: var(--c-text-subtle);
}
/* Icon-based chevrons */
@@ -151,13 +139,13 @@
.selectedList {
max-height: 8rem;
overflow: auto;
background-color: var(--bg-raised);
border: 0.0625rem solid var(--border-default);
background-color: var(--c-surface-raised);
border: 0.0625rem solid var(--c-border);
border-radius: 0.75rem;
padding: 0.5rem 0.75rem;
margin-top: 0.5rem;
min-width: 24rem;
color: var(--text-primary);
color: var(--c-text);
}
.selectedText {
@@ -175,7 +163,7 @@
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-bottom: 0.0625rem solid var(--border-default);
border-bottom: 0.0625rem solid var(--c-border);
margin-bottom: 0.5rem;
}
@@ -193,26 +181,18 @@
}
.advancedItem:hover {
background-color: var(--hover-bg);
}
:global([data-mantine-color-scheme="dark"]) .advancedItem:hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.advancedCard {
background-color: var(--bulk-card-bg);
background-color: var(--c-surface);
border: none;
border-radius: 0.75rem;
padding: 0.25rem;
margin-bottom: 0.5rem;
width: 100%;
box-sizing: border-box;
color: var(--text-primary);
}
:global([data-mantine-color-scheme="dark"]) .advancedCard {
background-color: var(--bulk-card-bg);
color: var(--c-text);
}
.inputGroup {
@@ -226,24 +206,17 @@
.applyButton {
min-width: 4rem;
flex-shrink: 0;
}
/* Style inputs and buttons within advanced cards to match bg-raised */
.advancedCard :global(.mantine-NumberInput-input) {
background-color: var(--bg-raised) !important;
border-color: var(--border-default) !important;
color: var(--text-primary) !important;
}
} /* Style inputs and buttons within advanced cards to match bg-raised */
.advancedCard :global(.mantine-NumberInput-input),
.advancedCard :global(.mantine-Button-root) {
background-color: var(--bg-raised) !important;
border-color: var(--border-default) !important;
color: var(--text-primary) !important;
background-color: var(--c-surface-raised) !important;
border-color: var(--c-border) !important;
color: var(--c-text) !important;
}
.advancedCard :global(.mantine-Button-root:hover) {
background-color: var(--hover-bg) !important;
border-color: var(--border-strong) !important;
background-color: var(--c-hover) !important;
border-color: var(--c-border-strong) !important;
}
/* Error helper text above the input */
@@ -254,8 +227,8 @@
/* Compact error container for inline tool settings */
.errorCompact {
background-color: var(--bg-raised);
border: 0.0625rem solid var(--border-default);
background-color: var(--c-surface-raised);
border: 0.0625rem solid var(--c-border);
border-radius: 0.75rem;
padding: 0.5rem 0.75rem;
margin-top: 0.5rem;
@@ -271,11 +244,6 @@
overflow: hidden;
}
/* Dark-mode adjustments */
:global([data-mantine-color-scheme="dark"]) .selectedList {
background-color: var(--bg-raised);
}
/* Small screens: allow the section to shrink instead of enforcing a large min width */
@media (max-width: 480px) {
.panelGroup,
@@ -290,16 +258,16 @@
.panelContainer {
max-height: 95vh;
overflow: auto;
background-color: var(--bulk-panel-bg);
color: var(--text-primary);
background-color: var(--c-surface);
color: var(--c-text);
border-radius: 0.5rem;
}
/* Override Mantine Popover dropdown background */
:global(.mantine-Popover-dropdown) {
background-color: var(--bulk-panel-bg) !important;
background-color: var(--c-surface) !important;
border-color: var(--bulk-card-border) !important;
color: var(--text-primary) !important;
color: var(--c-text) !important;
}
/* Override Mantine Switch outline */
@@ -17,7 +17,7 @@ const OperatorsSection = ({
return (
<div>
<Text size="xs" c="var(--text-muted)" fw={500} mb="xs">
<Text size="xs" c="var(--c-text-subtle)" fw={500} mb="xs">
{t("bulkSelection.keywords.title", "Keywords")}:
</Text>
<Group gap="sm" wrap="nowrap">
@@ -44,7 +44,7 @@ const PageSelectionInput = ({
icon="gpp-maybe-outline-rounded"
width="1rem"
height="1rem"
style={{ color: "var(--text-instruction)" }}
style={{ color: "var(--c-accent-fg)" }}
/>
<Text>
{t("bulkSelection.pageSelection.title", "Page Selection")}
@@ -53,7 +53,7 @@ const PageSelectionInput = ({
</Tooltip>
{typeof advancedOpened === "boolean" && (
<Flex align="center" gap="xs">
<Text size="sm" c="var(--text-secondary)">
<Text size="sm" c="var(--c-text-muted)">
{t("bulkSelection.advanced.title", "Advanced")}
</Text>
<Switch
@@ -50,7 +50,7 @@ const SelectPages = ({
return (
<div className={classes.advancedCard}>
<Text size="sm" fw={600} c="var(--text-secondary)" mb="xs">
<Text size="sm" fw={600} c="var(--c-text-muted)" mb="xs">
{title}
</Text>
{error && (
@@ -63,7 +63,8 @@
}
.modal-nav-scroll:hover {
scrollbar-color: rgba(128, 128, 128, 0.5) transparent;
scrollbar-color: color-mix(in srgb, var(--p-gray-mid) 50%, transparent)
transparent;
}
.modal-nav-scroll::-webkit-scrollbar {
@@ -81,11 +82,11 @@
}
.modal-nav-scroll:hover::-webkit-scrollbar-thumb {
background-color: rgba(128, 128, 128, 0.5);
background-color: color-mix(in srgb, var(--p-gray-mid) 50%, transparent);
}
.modal-nav-scroll::-webkit-scrollbar-thumb:hover {
background-color: rgba(128, 128, 128, 0.7);
background-color: color-mix(in srgb, var(--p-gray-mid) 70%, transparent);
}
.modal-nav-section {
@@ -131,7 +132,8 @@
}
.modal-content-scroll:hover {
scrollbar-color: rgba(128, 128, 128, 0.5) transparent;
scrollbar-color: color-mix(in srgb, var(--p-gray-mid) 50%, transparent)
transparent;
}
.modal-content-scroll::-webkit-scrollbar {
@@ -149,11 +151,11 @@
}
.modal-content-scroll:hover::-webkit-scrollbar-thumb {
background-color: rgba(128, 128, 128, 0.5);
background-color: color-mix(in srgb, var(--p-gray-mid) 50%, transparent);
}
.modal-content-scroll::-webkit-scrollbar-thumb:hover {
background-color: rgba(128, 128, 128, 0.7);
background-color: color-mix(in srgb, var(--p-gray-mid) 70%, transparent);
}
.modal-header {
@@ -212,8 +214,8 @@
bottom: 0;
left: 0;
right: 0;
background: var(--modal-content-bg);
border-top: 1px solid var(--modal-header-border);
background: var(--c-surface);
border-top: 1px solid var(--c-border-subtle);
padding: 1rem 2rem;
margin: 0 -2rem;
margin-bottom: -1rem;
@@ -165,13 +165,13 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
const colors = useMemo(
() => ({
navBg: "var(--modal-nav-bg)",
sectionTitle: "var(--modal-nav-section-title)",
navBg: "var(--c-bg-raised)",
sectionTitle: "var(--c-text-subtle)",
navItem: "var(--modal-nav-item)",
navItemActive: "var(--modal-nav-item-active)",
navItemActiveBg: "var(--modal-nav-item-active-bg)",
contentBg: "var(--modal-content-bg)",
headerBorder: "var(--modal-header-border)",
navItemActive: "var(--c-accent-fg)",
navItemActiveBg: "var(--c-primary-subtle)",
contentBg: "var(--c-surface)",
headerBorder: "var(--c-border-subtle)",
}),
[],
);
@@ -9,15 +9,15 @@
background: none;
cursor: pointer;
border-radius: var(--radius-sm);
color: var(--color-text-4);
color: var(--c-text-subtle);
transition:
background var(--motion-fast),
color var(--motion-fast);
}
.app-switch-btn:hover {
background: var(--color-bg-hover);
color: var(--color-text-2);
background: var(--c-hover);
color: var(--c-text-muted);
}
.app-switch-icon {
@@ -79,8 +79,8 @@ const Badge: React.FC<BadgeProps> = ({
// Default styling
return {
background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
background: "var(--c-surface-raised)",
color: "var(--c-accent-fg)",
};
};
@@ -78,7 +78,11 @@ export default function EditableSecretField({
)}
{description && (
<p
style={{ margin: "4px 0 12px 0", fontSize: "0.75rem", color: "#666" }}
style={{
margin: "4px 0 12px 0",
fontSize: "0.75rem",
color: "var(--c-text-muted)",
}}
>
{description}
</p>
@@ -117,7 +117,7 @@ export default class ErrorBoundary extends React.Component<
style={{
fontSize: "0.75rem",
overflow: "auto",
backgroundColor: "#f5f5f5",
backgroundColor: "var(--p-gray-100)",
padding: "1rem",
borderRadius: "4px",
maxHeight: "300px",
@@ -74,7 +74,7 @@ const FileCard = ({
<Stack gap={6} align="center">
<Box
style={{
border: "2px solid #e0e0e0",
border: "2px solid var(--c-border)",
borderRadius: 8,
width: 90,
height: 120,
@@ -82,7 +82,7 @@ const FileCard = ({
alignItems: "center",
justifyContent: "center",
margin: "0 auto",
background: "#fafbfc",
background: "var(--c-surface)",
position: "relative",
}}
>
@@ -54,8 +54,8 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
</Menu.Target>
<Menu.Dropdown
style={{
backgroundColor: "var(--bg-file-manager)",
border: "1px solid var(--border-subtle)",
backgroundColor: "var(--c-bg)",
border: "1px solid var(--c-border-subtle)",
borderRadius: "8px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
maxHeight: "50vh",
@@ -1,9 +1,9 @@
/* ── Trigger ─────────────────────────────────────── */
.trigger {
border: 1px solid var(--border-default);
border: 1px solid var(--c-border);
border-radius: var(--radius-md);
padding: 0.5rem 0.75rem;
background: var(--bg-surface);
background: var(--c-surface);
cursor: pointer;
flex: 1;
min-width: 0;
@@ -47,8 +47,8 @@
justify-content: space-between;
padding: 0.3rem 0.45rem;
gap: 0.4rem;
border-bottom: 1px solid var(--border-default);
background: var(--bg-surface);
border-bottom: 1px solid var(--c-border);
background: var(--c-surface);
}
.tabGroup {
@@ -66,7 +66,7 @@
min-width: 0;
min-height: 24px;
max-height: 26px;
border: 1px solid var(--border-default);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background: color-mix(
in srgb,
@@ -185,8 +185,8 @@
/* ── Search / filter row ─────────────────────────── */
.searchRow {
padding: 0.3rem 0.45rem;
border-bottom: 1px solid var(--border-default);
background: var(--bg-surface);
border-bottom: 1px solid var(--c-border);
background: var(--c-surface);
}
.searchInput {
@@ -195,7 +195,7 @@
height: 1.625rem;
padding: 0 0.5rem;
font-size: 0.7rem;
border: 1px solid var(--border-subtle, var(--border-default));
border: 1px solid var(--c-border-subtle, var(--c-border));
border-radius: var(--radius-sm);
background: var(--mantine-color-default-hover);
color: var(--mantine-color-text);
@@ -233,7 +233,7 @@
cursor: pointer;
background: transparent;
border: none;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--c-border);
text-align: left;
transition: background 0.08s ease;
}
@@ -1,8 +1,8 @@
/* ========== FILE SIDEBAR ========== */
.file-sidebar {
background-color: var(--bg-toolbar);
border-right: 1px solid var(--border-subtle);
background-color: var(--c-bg-raised);
border-right: 1px solid var(--c-border-subtle);
display: flex;
flex-direction: column;
height: 100%;
@@ -24,7 +24,7 @@
/* ---- Native file drag-and-drop ---- */
.file-sidebar[data-file-drag-over] {
outline: 2px dashed var(--mantine-color-blue-6, #3b82f6);
outline: 2px dashed var(--mantine-color-blue-6, var(--p-blue-500));
outline-offset: -4px;
}
@@ -42,20 +42,20 @@
pointer-events: none;
background-color: color-mix(
in srgb,
var(--mantine-color-blue-6, #3b82f6) 12%,
var(--bg-toolbar)
var(--mantine-color-blue-6, var(--p-blue-500)) 12%,
var(--c-bg-raised)
);
}
.file-sidebar-drop-overlay-icon {
color: var(--mantine-color-blue-6, #3b82f6) !important;
color: var(--mantine-color-blue-6, var(--p-blue-500)) !important;
font-size: 28px !important;
}
.file-sidebar-drop-overlay-text {
font-size: 13px;
font-weight: 600;
color: var(--mantine-color-blue-6, #3b82f6);
color: var(--mantine-color-blue-6, var(--p-blue-500));
}
/* ---- Header ---- */
@@ -75,11 +75,11 @@
/* Icons stay left-aligned during animation; overflow:hidden on inner clips text naturally */
.file-sidebar-header:hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.file-sidebar-menu-icon {
color: var(--text-muted) !important;
color: var(--c-text-subtle) !important;
font-size: 18px !important;
flex-shrink: 0;
}
@@ -129,11 +129,11 @@
}
.file-sidebar-search-row:not(.active):hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.file-sidebar-search-icon {
color: var(--text-muted) !important;
color: var(--c-text-subtle) !important;
font-size: 18px !important;
flex-shrink: 0;
}
@@ -148,19 +148,19 @@
border: none;
outline: none;
font-size: 14px;
color: var(--text-primary);
color: var(--c-text);
margin-left: 12px;
min-width: 0;
}
.file-sidebar-search-input::placeholder {
color: var(--text-muted);
color: var(--c-text-subtle);
}
.file-sidebar-search-label {
margin-left: 12px;
font-size: 14px;
color: var(--text-secondary);
color: var(--c-text-muted);
}
/* ---- Scrollable content ---- */
@@ -199,7 +199,7 @@
}
.file-sidebar-action-row:hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.file-sidebar-action-row.disabled {
@@ -212,7 +212,7 @@
}
.file-sidebar-action-icon {
color: var(--text-muted) !important;
color: var(--c-text-subtle) !important;
font-size: 18px !important;
flex-shrink: 0;
display: inline-flex;
@@ -234,7 +234,7 @@
.file-sidebar-action-label {
margin-left: 12px;
font-size: 14px;
color: var(--text-secondary);
color: var(--c-text-muted);
white-space: nowrap;
}
@@ -253,7 +253,7 @@
}
.file-sidebar-cloud-row:not(.disabled):hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.file-sidebar-cloud-row.disabled {
@@ -320,19 +320,19 @@
border: none;
background: transparent;
cursor: pointer;
color: var(--text-default, var(--color-text-1));
color: var(--text-default, var(--c-text));
border-radius: 0;
font-size: 13px;
font-weight: 600;
text-align: left;
}
.file-sidebar-group-header:hover {
background: var(--hover-bg, var(--color-bg-hover));
background: var(--c-hover, var(--c-hover));
}
.file-sidebar-group-icon {
flex: none;
color: var(--text-muted, var(--color-text-3));
color: var(--c-text-subtle, var(--c-text-subtle));
}
.file-sidebar-group-label {
@@ -347,7 +347,7 @@
flex: none;
font-size: 11px;
font-weight: 500;
color: var(--text-muted, var(--color-text-3));
color: var(--c-text-subtle, var(--c-text-subtle));
}
/* Full-width, flat rows: no inset, no radius, so the hover/selected highlight
@@ -363,7 +363,10 @@
}
/* Hover reads neutral (not the default blue tint); selected stays blue. */
.file-sidebar-group-items .file-sidebar-file-item:hover:not(.selected) {
background-color: var(--color-bg-hover, rgba(120, 120, 120, 0.1));
background-color: var(
--c-hover,
color-mix(in srgb, var(--p-gray-mid) 10%, transparent)
);
}
/* A plain circle where the selected check sits — on hover, and blue when selected. */
.file-sidebar-group-items .file-sidebar-file-checkbox-hover,
@@ -413,7 +416,7 @@
border: none;
background: transparent;
cursor: pointer;
color: var(--color-blue, #2563eb);
color: var(--c-primary, var(--p-blue-600));
font-size: 12px;
font-weight: 500;
border-radius: 6px;
@@ -424,7 +427,10 @@
display: none;
}
.file-sidebar-view-all:hover {
background: var(--color-bg-hover, rgba(120, 120, 120, 0.1));
background: var(
--c-hover,
color-mix(in srgb, var(--p-gray-mid) 10%, transparent)
);
}
.file-sidebar-file-list::-webkit-scrollbar {
@@ -438,11 +444,11 @@
border-radius: 2px;
}
.file-sidebar-file-list::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
background: var(--c-text-subtle);
}
.file-sidebar-section-divider {
border-top: 1px solid var(--border-subtle);
border-top: 1px solid var(--c-border-subtle);
margin: 10px 14px 6px 14px;
}
@@ -457,7 +463,7 @@
.file-sidebar-section-header {
position: relative;
border-top: 1px solid var(--border-subtle);
border-top: 1px solid var(--c-border-subtle);
padding-top: 10px;
margin-top: 4px;
flex-shrink: 0;
@@ -467,7 +473,7 @@
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--text-muted);
color: var(--c-text-subtle);
text-transform: uppercase;
}
@@ -481,7 +487,7 @@
align-items: center;
justify-content: space-between;
font-size: 11px;
color: var(--text-muted);
color: var(--c-text-subtle);
margin-bottom: 4px;
}
.file-sidebar-bulk-add-count {
@@ -490,19 +496,23 @@
.file-sidebar-bulk-add-track {
height: 3px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-blue, #3b82f6) 15%, transparent);
background: color-mix(
in srgb,
var(--c-primary, var(--p-blue-500)) 15%,
transparent
);
overflow: hidden;
}
.file-sidebar-bulk-add-bar {
height: 100%;
border-radius: 999px;
background: var(--color-blue, #3b82f6);
background: var(--c-primary, var(--p-blue-500));
transition: width 0.15s ease-out;
}
.file-sidebar-section-count {
font-size: 11px;
color: var(--text-muted);
color: var(--c-text-subtle);
}
.file-sidebar-section-btn {
@@ -515,7 +525,7 @@
background: transparent;
border-radius: 4px;
cursor: pointer;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 0;
outline: none;
transition:
@@ -525,13 +535,13 @@
}
.file-sidebar-section-btn:focus-visible {
outline: 2px solid #3b82f6;
outline: 2px solid var(--c-primary);
outline-offset: 1px;
}
.file-sidebar-section-btn:hover {
background-color: var(--hover-bg);
color: var(--text-primary);
background-color: var(--c-hover);
color: var(--c-text);
}
.file-sidebar-section-btn-external {
@@ -576,7 +586,7 @@
align-items: center;
gap: 8px;
padding: 8px 10px;
border-top: 1px solid var(--border-subtle);
border-top: 1px solid var(--c-border-subtle);
flex-shrink: 0;
min-height: 48px;
}
@@ -587,7 +597,7 @@
width: 28px;
height: 28px;
border-radius: 50%;
background-color: var(--mantine-color-blue-6, #3b82f6);
background-color: var(--mantine-color-blue-6, var(--p-blue-500));
color: #fff;
font-size: 12px;
font-weight: 600;
@@ -615,7 +625,7 @@
flex: 1;
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
color: var(--c-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -623,11 +633,11 @@
}
.file-sidebar-bottom-bar[role="button"]:hover {
background-color: var(--hover-bg);
background-color: var(--c-hover);
}
.file-sidebar-bottom-bar[role="button"]:focus-visible {
outline: 2px solid #3b82f6;
outline: 2px solid var(--c-primary);
outline-offset: -2px;
}
@@ -638,7 +648,7 @@
width: 28px;
height: 28px;
border-radius: 6px;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 0;
flex-shrink: 0;
margin-left: auto;
@@ -1114,7 +1114,7 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
<div className="file-sidebar-cloud-icon-wrapper">
<GoogleDriveIcon
className="file-sidebar-cloud-icon-gray"
style={{ color: "var(--text-secondary)" }}
style={{ color: "var(--c-text-muted)" }}
/>
{isGoogleDriveEnabled && (
<GoogleDriveIcon
@@ -1145,7 +1145,7 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
aria-label={t("watchedFolders.sidebarTitle", "Watched Folders")}
style={
isWatchedFoldersActive
? { backgroundColor: "var(--active-bg)" }
? { backgroundColor: "var(--c-active)" }
: undefined
}
>
@@ -1197,7 +1197,7 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
{!stubsLoaded ? (
<div className="file-sidebar-loading">
<Loader size="sm" color="var(--text-muted)" />
<Loader size="sm" color="var(--c-text-subtle)" />
</div>
) : filteredFileStubs.length > 0 ? (
<div className="file-sidebar-file-list">
@@ -17,7 +17,7 @@
background: transparent;
}
.file-sidebar-file-list::-webkit-scrollbar-thumb {
background: rgb(var(--border));
background: var(--c-border);
border-radius: 2px;
}
@@ -64,15 +64,15 @@
}
.file-sidebar-file-item:hover:not(.selected) {
background-color: rgba(59, 130, 246, 0.06);
background-color: color-mix(in srgb, var(--c-primary) 6%, transparent);
}
.file-sidebar-file-item.selected {
background-color: rgba(59, 130, 246, 0.12);
background-color: color-mix(in srgb, var(--c-primary) 12%, transparent);
}
.file-sidebar-file-item.active:not(.selected) {
background-color: rgba(59, 130, 246, 0.06);
background-color: color-mix(in srgb, var(--c-primary) 6%, transparent);
}
/* Icon wrapper */
@@ -96,7 +96,7 @@
width: 20px;
height: 20px;
border-radius: 5px;
border: 1.5px solid var(--border-hover, var(--border-strong));
border: 1.5px solid var(--c-border-strong);
}
.file-sidebar-file-item:hover .file-sidebar-file-checkbox-hover {
@@ -117,7 +117,7 @@
width: 20px;
height: 20px;
border-radius: 5px;
background-color: #3b82f6;
background-color: var(--c-primary);
display: flex;
align-items: center;
justify-content: center;
@@ -125,7 +125,7 @@
}
.file-sidebar-check-svg {
color: white;
color: var(--c-text-on-primary);
flex-shrink: 0;
}
@@ -180,14 +180,14 @@
display: block;
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
color: var(--c-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-sidebar-file-item.selected .file-sidebar-file-name {
color: #3b82f6;
color: var(--c-accent-fg);
}
.file-sidebar-file-meta-row {
@@ -200,7 +200,7 @@
.file-sidebar-file-meta {
font-size: 11px;
color: var(--text-muted);
color: var(--c-text-subtle);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -212,7 +212,7 @@
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--accent-interactive, #6366f1);
color: var(--c-primary);
}
/* ---- Folder membership tags ---- */
@@ -251,7 +251,7 @@
font-size: 10px;
font-weight: 600;
line-height: 1.2;
color: var(--text-secondary);
color: var(--c-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -261,7 +261,7 @@
font-size: 10px;
font-weight: 600;
line-height: 1;
color: var(--text-muted);
color: var(--c-text-subtle);
cursor: default;
flex-shrink: 0;
}
@@ -278,7 +278,7 @@
background: transparent;
border-radius: 4px;
cursor: pointer;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 0;
opacity: 0;
transition:
@@ -298,48 +298,45 @@
bottom: 0;
z-index: 2;
/* solid base + green tint overlay so content behind doesn't bleed through */
background-color: var(--bg-toolbar);
background-color: var(--c-bg-raised);
background-image: linear-gradient(
rgba(34, 197, 94, 0.1),
rgba(34, 197, 94, 0.1)
color-mix(in srgb, var(--p-green-500) 10%, transparent),
color-mix(in srgb, var(--p-green-500) 10%, transparent)
);
}
.file-sidebar-file-item.viewed .file-sidebar-file-name {
color: #22c55e;
color: var(--p-green-500);
}
.file-sidebar-file-item.viewed .file-sidebar-file-check {
background-color: #22c55e;
background-color: var(--p-green-500);
}
/* viewed takes precedence over selected */
.file-sidebar-file-item.viewed.selected {
background-color: var(--bg-toolbar);
background-color: var(--c-bg-raised);
background-image: linear-gradient(
rgba(34, 197, 94, 0.1),
rgba(34, 197, 94, 0.1)
color-mix(in srgb, var(--p-green-500) 10%, transparent),
color-mix(in srgb, var(--p-green-500) 10%, transparent)
);
}
/* Always show eye for the currently viewed file */
.file-sidebar-file-item.viewed .file-sidebar-eye-btn {
opacity: 1;
color: #22c55e;
color: var(--p-green-500);
}
.file-sidebar-eye-btn:hover {
color: var(--text-primary);
color: var(--c-text);
}
/* Eye open: shown by default */
.file-sidebar-eye-open {
display: block !important;
}
.file-sidebar-eye-closed {
display: none !important;
}
.file-sidebar-eye-closed,
/* On hover over a viewed item: swap to eye-closed */
.file-sidebar-file-item.viewed:hover .file-sidebar-eye-open {
display: none !important;
@@ -360,7 +357,7 @@
background: transparent;
border-radius: 4px;
cursor: pointer;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 0;
opacity: 0;
transition:
@@ -375,14 +372,14 @@
}
.file-sidebar-kebab-btn:hover {
color: var(--text-primary);
color: var(--c-text);
}
/* ---- Date group headers ---- */
.file-sidebar-date-group-header {
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
color: var(--c-text-subtle);
padding: 0 10px 2px 10px;
margin-top: 8px;
user-select: none;
@@ -409,13 +406,13 @@
.file-sidebar-empty-text {
font-size: 12px;
color: var(--text-muted);
color: var(--c-text-subtle);
margin: 0 0 2px 0;
}
.file-sidebar-empty-hint {
font-size: 11px;
color: var(--text-muted);
color: var(--c-text-subtle);
opacity: 0.7;
margin: 0;
}
@@ -425,8 +422,8 @@
position: fixed;
z-index: 9999;
transform: translateY(-50%);
background: var(--bg-surface, #fff);
border: 1px solid var(--border-subtle);
background: var(--c-surface);
border: 1px solid var(--c-border-subtle);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
padding: 4px;
@@ -54,8 +54,8 @@ export default function Footer({
<div
style={{
height: "var(--footer-height)",
backgroundColor: "var(--bg-surface)",
borderTop: "1px solid var(--border-subtle)",
backgroundColor: "var(--c-surface)",
borderTop: "1px solid var(--c-border-subtle)",
display: "flex",
flexDirection: "column",
justifyContent: "center",
@@ -6,8 +6,8 @@
display: flex;
gap: 8px;
align-items: center;
background: var(--bg-toolbar);
border: 1px solid var(--border-default);
background: var(--c-bg-raised);
border: 1px solid var(--c-border);
padding: 6px 12px;
border-radius: 20px;
box-shadow: var(--shadow-md);
@@ -70,7 +70,7 @@ const HoverActionMenu: React.FC<HoverActionMenuProps> = ({
disabled={action.disabled}
onClick={action.onClick}
aria-label={action.label}
style={{ color: action.color || "var(--text-secondary)" }}
style={{ color: action.color || "var(--c-text-muted)" }}
data-tour={action.dataTour}
>
{action.icon}
@@ -34,12 +34,7 @@ export function LandingActions({
<Button
className="landing-btn-primary"
leftSection={
<LocalIcon
icon={icons.uploadIconName}
width="1rem"
height="1rem"
style={{ color: "white" }}
/>
<LocalIcon icon={icons.uploadIconName} width="1rem" height="1rem" />
}
onClick={(e) => {
e.stopPropagation();
@@ -52,14 +47,7 @@ export function LandingActions({
<Button
variant="secondary"
className="landing-btn-secondary"
leftSection={
<LocalIcon
icon="add"
width="1rem"
height="1rem"
className="text-[var(--accent-interactive)]"
/>
}
leftSection={<LocalIcon icon="add" width="1rem" height="1rem" />}
onClick={(e) => {
e.stopPropagation();
openFilesModal();
@@ -10,7 +10,7 @@
font-size: 2rem;
font-weight: 700;
line-height: 1.25;
color: var(--text-primary);
color: var(--c-text);
}
.landing-subtitle {
@@ -20,7 +20,7 @@
font-size: 0.9375rem;
line-height: 1.5;
max-width: 28rem;
color: var(--text-secondary);
color: var(--c-text-muted);
}
/* ── Document stack ──────────────────────────────────────── */
@@ -49,7 +49,7 @@
width: 128px;
height: 160px;
transform-origin: bottom center;
border: 1px solid #e5e7eb;
border: 1px solid var(--p-gray-200);
box-shadow: var(--landing-doc-shadow-back-idle);
}
@@ -103,16 +103,16 @@
/* Bars — static light colours, never change with theme */
.landing-bar {
border-radius: 9999px;
background-color: #e5e7eb;
background-color: var(--p-gray-200);
}
.landing-bar--strong {
background-color: #d1d5db;
background-color: var(--p-gray-300);
}
/* ── Action buttons ──────────────────────────────────────── */
.landing-btn-primary {
background: var(--landing-hero-gradient) !important;
color: #ffffff !important;
color: var(--c-text-on-primary) !important;
border: none !important;
border-radius: 0.75rem !important;
font-weight: 600 !important;
@@ -121,27 +121,27 @@
.landing-btn-secondary {
border-radius: 0.75rem !important;
font-weight: 600 !important;
border-color: var(--landing-button-border, var(--border-default)) !important;
background-color: var(--landing-button-bg, var(--bg-surface)) !important;
color: var(--landing-button-color, var(--text-primary)) !important;
border-color: var(--landing-button-border, var(--c-border)) !important;
background-color: var(--landing-button-bg, var(--c-surface)) !important;
color: var(--c-accent-fg, var(--c-text)) !important;
}
.landing-btn-secondary:hover {
background-color: var(
--landing-button-hover-bg,
var(--landing-button-bg, var(--bg-surface))
var(--landing-button-bg, var(--c-surface))
) !important;
}
/* Icon-only variant: accent colour instead of button text colour */
.landing-btn-icon {
color: var(--accent-interactive) !important;
color: var(--c-primary) !important;
}
/* Dropzone accept/reject outlines. Mantine 8 no longer supports nested
* `&[data-accept]` selectors inside the `styles` prop object, so these are
* plain CSS attribute selectors on a class applied to the Dropzone root. */
.landing-dropzone[data-accept] {
outline: 2px dashed var(--accent-interactive);
outline: 2px dashed var(--c-primary);
outline-offset: 4px;
}
.landing-dropzone[data-reject] {
@@ -6,7 +6,7 @@
}
.languageItem {
border-right: 2px solid var(--mantine-color-gray-3);
border-right: 2px solid var(--c-border);
}
.languageItem:nth-child(4n) {
@@ -20,7 +20,7 @@
}
.languageItem:nth-child(4n) {
border-right: 2px solid var(--mantine-color-gray-3);
border-right: 2px solid var(--c-border);
}
.languageItem:nth-child(2n) {
@@ -34,7 +34,7 @@
}
.languageItem:nth-child(4n) {
border-right: 2px solid var(--mantine-color-gray-3);
border-right: 2px solid var(--c-border);
}
.languageItem:nth-child(3n) {
@@ -42,23 +42,12 @@
}
}
/* Dark theme support */
[data-mantine-color-scheme="dark"] .languageItem {
border-right-color: var(--mantine-color-dark-3);
}
/* Dark theme: divider colour comes from the adaptive --c-border token on the
base rules; only the layout quirk (no right border on 4n) differs. */
[data-mantine-color-scheme="dark"] .languageItem:nth-child(4n) {
border-right: none;
}
[data-mantine-color-scheme="dark"] .languageItem:nth-child(2n) {
border-right-color: var(--mantine-color-dark-3);
}
[data-mantine-color-scheme="dark"] .languageItem:nth-child(3n) {
border-right-color: var(--mantine-color-dark-3);
}
/* Responsive text visibility */
.languageText {
display: none;
@@ -91,10 +91,10 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
? "light-dark(var(--mantine-color-blue-1), var(--mantine-color-blue-8))"
: undefined,
color: disabled
? "var(--text-muted)"
? "var(--c-text-subtle)"
: isSelected
? "light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-3))"
: "var(--text-primary)",
: "var(--c-text)",
}}
>
{label}
@@ -10,9 +10,12 @@ export function LoadingFallback() {
alignItems: "center",
height: "100vh",
fontSize: "18px",
// Theme-aware so the splash follows light/dark instead of forcing white.
backgroundColor: "var(--mantine-color-body)",
color: "var(--mantine-color-text)",
// Use our own tokens, not --mantine-color-*: this splash renders inside
// Suspense before MantineProvider sets its scheme, so --mantine-color-body
// is still Mantine's light default (white flash in dark mode). --c-bg/
// --c-text come from the pre-paint attributes on <html>, so they're right.
backgroundColor: "var(--c-bg)",
color: "var(--c-text)",
}}
>
Loading...
@@ -12,7 +12,7 @@
padding: 16px;
color: #ffffff;
font-weight: 600;
background: rgba(16, 18, 27, 0.55);
background: color-mix(in srgb, var(--p-zinc-900) 55%, transparent);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.06);
@@ -197,8 +197,8 @@ export const PageEditorFileDropdown: React.FC<PageEditorFileDropdownProps> = ({
<Menu.Dropdown
className="ph-no-capture"
style={{
backgroundColor: "var(--bg-file-manager)",
border: "1px solid var(--border-subtle)",
backgroundColor: "var(--c-bg)",
border: "1px solid var(--c-border-subtle)",
borderRadius: "8px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
maxHeight: "80vh",
@@ -231,7 +231,7 @@ export const PageEditorFileDropdown: React.FC<PageEditorFileDropdownProps> = ({
marginTop: "0.5rem",
cursor: "pointer",
backgroundColor: "transparent",
borderTop: "1px solid var(--border-subtle)",
borderTop: "1px solid var(--c-border-subtle)",
transition: "background-color 0.15s ease",
}}
onMouseEnter={(e) => {
@@ -17,7 +17,7 @@
width: 15px;
height: 15px;
border-radius: 4px;
color: var(--text-secondary);
color: var(--c-text-muted);
background: color-mix(in srgb, currentColor 16%, transparent);
pointer-events: auto;
}
@@ -17,9 +17,13 @@ import {
type ThemeMode,
getSystemTheme,
resolveColorScheme,
DEFAULT_ACCENT,
DEFAULT_ACCENT_COLOR,
} from "@app/constants/theme";
import { deriveAccessiblePrimary } from "@app/utils/customPrimary";
// SUI shared design-system tokens (used by @app/ui); key on `data-theme`.
import "@app/tokens/tokens.css";
import "@app/theme/index.css";
interface ThemeContextType {
themeMode: ThemeMode;
@@ -68,13 +72,31 @@ export function ThemeProvider({ children }: ThemeProviderProps) {
return () => media.removeEventListener("change", update);
}, [themeMode]);
// The theme preference resolved to a concrete light/dark scheme.
// The mode resolved to a concrete light/dark base.
const colorScheme = resolveColorScheme(themeMode, systemScheme);
// Each base has its own accent; "system" picks by the resolved scheme.
const primaryInput =
colorScheme === "dark" ? preferences.darkPrimary : preferences.lightPrimary;
// Mantine drives `data-mantine-color-scheme`; mirror it to `data-theme` for SUI tokens.
// A colour tints surfaces; the "default" sentinel means neutral surfaces + blue buttons (derive from blue, flag data-accent="default" so colors.css skips the tint).
const isDefaultAccent = primaryInput === DEFAULT_ACCENT;
const accentColor = isDefaultAccent ? DEFAULT_ACCENT_COLOR : primaryInput;
// Mirror the scheme to data-theme (SUI) and set data-app-theme="custom" so colors.css derives from --user-primary.
useIsomorphicEffect(() => {
document.documentElement.setAttribute("data-theme", colorScheme);
}, [colorScheme]);
const root = document.documentElement;
root.setAttribute("data-theme", colorScheme);
root.setAttribute("data-app-theme", "custom");
root.setAttribute("data-accent", isDefaultAccent ? "default" : "custom");
// Clamp/contrast-pick the accent so it can't collapse into the base and stays legible.
const { primary, onPrimary, accentForeground } = deriveAccessiblePrimary(
accentColor,
colorScheme,
);
root.style.setProperty("--user-primary", primary);
root.style.setProperty("--user-primary-on", onPrimary);
root.style.setProperty("--user-accent-fg", accentForeground);
}, [colorScheme, accentColor, isDefaultAccent]);
const value = useMemo<ThemeContextType>(
() => ({ themeMode, setTheme }),
@@ -14,7 +14,7 @@ interface ToolIconProps {
export const ToolIcon: React.FC<ToolIconProps> = ({
icon,
opacity = 1,
color = "var(--tools-text-and-icon-color)",
color = "var(--c-text)",
marginRight = "0.5rem",
}) => {
return (
@@ -18,7 +18,7 @@
flex: 1;
min-width: 0;
padding: 0.4rem 0.75rem 0.4rem 0.4rem;
border: 1px solid var(--border-subtle, var(--mantine-color-default-border));
border: 1px solid var(--c-border-subtle, var(--mantine-color-default-border));
border-radius: 9999px;
background: var(--mantine-color-body);
text-align: left;
@@ -70,7 +70,7 @@
so it doesn't read as a clashing lighter card on the dark toolbar. */
[data-mantine-color-scheme="dark"] .sui-panelhdr__bar {
background: transparent;
border-color: var(--border-subtle, var(--mantine-color-default-border));
border-color: var(--c-border-subtle, var(--mantine-color-default-border));
}
[data-mantine-color-scheme="dark"] .sui-panelhdr__icon {
@@ -383,7 +383,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE,
visibility: positionReady ? "visible" : "hidden",
opacity: positionReady ? 1 : 0,
color: "var(--text-primary)",
color: "var(--c-text)",
...containerStyle,
}}
className={`${styles["tooltip-container"]} ${isPinned ? styles.pinned : ""}`}
@@ -291,7 +291,7 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
<Box
style={{
border:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
borderRadius: 12,
padding: "24px 28px",
background:
@@ -493,10 +493,10 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
style={{
borderTop:
idx === 0
? "1px solid var(--border-subtle, var(--mantine-color-default-border))"
? "1px solid var(--c-border-subtle, var(--mantine-color-default-border))"
: undefined,
borderBottom:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
padding: "10px 12px",
}}
>
@@ -576,10 +576,10 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
style={{
borderTop:
index === 0
? "1px solid var(--border-subtle, var(--mantine-color-default-border))"
? "1px solid var(--c-border-subtle, var(--mantine-color-default-border))"
: undefined,
borderBottom:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
}}
>
<Group
@@ -656,7 +656,7 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
pb="sm"
style={{
borderTop:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
}}
>
<Stack gap="sm" mt="sm">
@@ -749,7 +749,7 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
<Box
style={{
border:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
borderRadius: 12,
padding: "16px 20px",
}}
@@ -834,7 +834,7 @@ const UpdateModal: React.FC<UpdateModalProps> = ({
<Box
style={{
borderTop:
"1px solid var(--border-subtle, var(--mantine-color-default-border))",
"1px solid var(--c-border-subtle, var(--mantine-color-default-border))",
padding: "16px 28px",
flexShrink: 0,
}}
@@ -8,8 +8,8 @@
align-content: flex-start;
min-height: 40px;
padding: 0 8px;
background-color: var(--bg-toolbar);
border-bottom: 1px solid var(--border-subtle);
background-color: var(--c-bg-raised);
border-bottom: 1px solid var(--c-border-subtle);
flex-shrink: 0;
z-index: 50;
}
@@ -33,7 +33,7 @@
border: none;
border-radius: 8px;
background: transparent;
color: var(--text-secondary);
color: var(--c-text-muted);
font-size: 13px;
font-weight: 500;
cursor: pointer;
@@ -44,27 +44,23 @@
}
.workbench-bar-view-btn:hover {
background-color: var(--hover-bg);
color: var(--text-primary);
background-color: var(--c-hover);
color: var(--c-text);
}
.workbench-bar-view-btn.active {
background-color: var(--mantine-color-blue-light, rgba(59, 130, 246, 0.12));
color: var(--mantine-color-blue-6, #3b82f6);
background-color: var(--c-primary-subtle);
color: var(--c-accent-fg);
}
.workbench-bar-view-btn.workbench-bar-back-btn {
color: var(--text-primary);
color: var(--c-text);
font-weight: 600;
}
.workbench-bar-view-btn.workbench-bar-back-btn:hover {
background-color: color-mix(
in srgb,
var(--accent-interactive, #4a90e2) 12%,
transparent
);
color: var(--accent-interactive, #4a90e2);
background-color: color-mix(in srgb, var(--c-primary) 12%, transparent);
color: var(--c-accent-fg);
}
.workbench-bar-view-btn svg {
@@ -106,7 +102,7 @@
flex: 0 0 100%;
height: auto;
padding: 4px 0;
border-top: 1px solid var(--border-subtle);
border-top: 1px solid var(--c-border-subtle);
justify-content: flex-start;
overflow-x: auto;
overflow-y: hidden;
@@ -138,7 +134,7 @@
* Tight 24px hit-box and no hover background: these are quiet toolbar icons —
* hover feedback comes from the icon colour darkening, not a bg tint. */
.workbench-bar-action-icon {
color: var(--text-secondary) !important;
color: var(--c-text-muted) !important;
/* min-* too: Mantine's ActionIcon sets min-width/min-height from --ai-size
* (36px), which floors the width and defeats the 24px clamp. */
width: 24px !important;
@@ -155,17 +151,17 @@
}
.workbench-bar-action-icon:hover {
color: var(--text-primary) !important;
color: var(--c-text) !important;
}
.workbench-bar-action-icon[data-variant="filled"] {
background: var(--mantine-color-blue-6, #3b82f6) !important;
color: #fff !important;
background: var(--c-primary) !important;
color: var(--c-text-on-primary) !important;
}
.workbench-bar-action-icon[data-variant="filled"]:hover {
background: var(--mantine-color-blue-7, #2563eb) !important;
color: #fff !important;
background: var(--c-primary-hover) !important;
color: var(--c-text-on-primary) !important;
}
.workbench-bar-action-wrapper {
@@ -181,7 +177,7 @@
.workbench-bar-divider {
width: 1px;
height: 16px;
background-color: var(--border-subtle);
background-color: var(--c-border-subtle);
margin: 0 4px;
}
@@ -200,7 +196,7 @@
.viewer-inline-controls__page {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
color: var(--c-text-muted);
min-width: 3rem;
text-align: center;
white-space: nowrap;
@@ -214,7 +210,7 @@
.viewer-inline-controls__zoom-pct {
font-size: 12px;
color: var(--text-muted);
color: var(--c-text-subtle);
min-width: 2.5rem;
text-align: right;
white-space: nowrap;
@@ -14,17 +14,23 @@ import {
} from "@mantine/core";
import { Button } from "@app/ui/Button";
import { ActionIcon } from "@app/ui/ActionIcon";
import { ColorGridPicker } from "@app/ui/ColorGridPicker";
import { SegmentedControl } from "@app/ui/SegmentedControl";
import { useTranslation } from "react-i18next";
import { usePreferences } from "@app/contexts/PreferencesContext";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import { useTheme } from "@app/components/shared/ThemeProvider";
import LanguageSelector from "@app/components/shared/LanguageSelector";
import type { ThemeMode } from "@app/constants/theme";
import {
THEME_ACCENT_PRESETS,
DEFAULT_ACCENT,
type ThemeMode,
} from "@app/constants/theme";
import type { ToolPanelMode } from "@app/constants/toolPanel";
import type {
StartupView,
ViewerZoomSetting,
import {
DEFAULT_PREFERENCES,
type StartupView,
type ViewerZoomSetting,
} from "@app/services/preferencesService";
import { Z_INDEX_OVER_CONFIG_MODAL } from "@app/styles/zIndex";
import LocalIcon from "@app/components/shared/LocalIcon";
@@ -75,6 +81,39 @@ interface GeneralSectionProps {
desktopUpdateMode?: DesktopUpdateModeControl;
}
/** Accent-colour picker: a swatch grid — a "Default" icon cell + the accent presets — via the shared ColorGridPicker. */
function AccentSwatchDropdown({
value,
onChange,
ariaLabel,
}: {
value: string;
onChange: (value: string) => void;
ariaLabel: string;
}) {
const { t } = useTranslation();
return (
<ColorGridPicker
value={value}
onChange={onChange}
colors={THEME_ACCENT_PRESETS}
ariaLabel={ariaLabel}
zIndex={Z_INDEX_OVER_CONFIG_MODAL}
defaultOption={{
// Default is not a colour — an icon chip signalling the neutral theme
// (surfaces untinted, blue buttons); stores the sentinel.
value: DEFAULT_ACCENT,
icon: "star-rounded",
label: t("settings.general.themeAccentDefault", "Default"),
hint: t(
"settings.general.themeAccentDefaultHint",
"Default theme (recommended)",
),
}}
/>
);
}
const GeneralSection: React.FC<GeneralSectionProps> = ({
hideTitle = false,
hideUpdateSection = false,
@@ -482,7 +521,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
<Text size="xs" c="dimmed" mt={4}>
{t(
"settings.general.themeDescription",
"Choose light, dark, or follow your system",
"Choose light, dark, or follow your system so it switches automatically.",
)}
</Text>
</div>
@@ -505,29 +544,119 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
]}
/>
</div>
<div
style={{
display: "flex",
alignItems: "center",
alignItems: "flex-start",
justifyContent: "space-between",
gap: "1rem",
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t("settings.general.language", "Language")}
{t("settings.general.themeAccent", "Accent colour")}
</Text>
<Text size="xs" c="dimmed" mt={4}>
{t(
"settings.general.languageDescription",
"Choose the display language",
"settings.general.themeAccentDescription",
"Buttons, links and highlights follow it, and it subtly tints the app. Light and dark each have their own — System uses whichever is active.",
)}
</Text>
</div>
<LanguageSelector position="bottom-end" offset={6} />
<Stack gap="md">
<Group
gap="sm"
wrap="nowrap"
align="flex-start"
justify="flex-end"
>
<Text
size="sm"
c="dimmed"
style={{ width: "3rem", paddingTop: 4 }}
>
{t("settings.general.themeLight", "Light")}
</Text>
<AccentSwatchDropdown
value={preferences.lightPrimary}
onChange={(val) => updatePreference("lightPrimary", val)}
ariaLabel={t(
"settings.general.themeAccentLight",
"Light mode accent colour",
)}
/>
</Group>
<Group
gap="sm"
wrap="nowrap"
align="flex-start"
justify="flex-end"
>
<Text
size="sm"
c="dimmed"
style={{ width: "3rem", paddingTop: 4 }}
>
{t("settings.general.themeDark", "Dark")}
</Text>
<AccentSwatchDropdown
value={preferences.darkPrimary}
onChange={(val) => updatePreference("darkPrimary", val)}
ariaLabel={t(
"settings.general.themeAccentDark",
"Dark mode accent colour",
)}
/>
</Group>
</Stack>
</div>
<Group justify="flex-end">
<Button
variant="quiet"
size="sm"
onClick={() => {
setTheme(DEFAULT_PREFERENCES.theme);
updatePreference(
"lightPrimary",
DEFAULT_PREFERENCES.lightPrimary,
);
updatePreference(
"darkPrimary",
DEFAULT_PREFERENCES.darkPrimary,
);
}}
>
{t("settings.general.themeReset", "Restore theme to default")}
</Button>
</Group>
</Stack>
</Paper>
{/* Language */}
<Paper withBorder p="md" radius="md">
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t("settings.general.language", "Language")}
</Text>
<Text size="xs" c="dimmed" mt={4}>
{t(
"settings.general.languageDescription",
"Choose the display language",
)}
</Text>
</div>
<LanguageSelector position="bottom-end" offset={6} />
</div>
</Paper>
<Paper withBorder p="md" radius="md">
<Stack gap="md">
<div
@@ -153,7 +153,7 @@ const DocumentThumbnail: React.FC<DocumentThumbnailProps> = ({
fontWeight: 700,
letterSpacing: "0.1em",
textTransform: "uppercase",
color: "var(--text-secondary)",
color: "var(--c-text-muted)",
background: "rgb(var(--border))",
padding: "3px 10px",
borderRadius: "6px",
@@ -15,7 +15,7 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--search-text-and-icon-color);
color: var(--c-text-subtle);
}
.input {
@@ -27,12 +27,12 @@
outline: none;
box-shadow: none;
transition: box-shadow 0.2s ease;
background-color: var(--input-bg);
color: var(--search-text-and-icon-color);
background-color: var(--c-input-bg);
color: var(--c-text-subtle);
}
.input::placeholder {
color: var(--search-text-and-icon-color);
color: var(--c-text-subtle);
opacity: 1;
}
@@ -65,13 +65,9 @@
justify-content: center;
font-size: 16px;
transition: background-color 0.2s ease;
color: var(--search-text-and-icon-color);
color: var(--c-text-subtle);
}
.clearButton:hover {
background-color: rgba(0, 0, 0, 0.1);
}
[data-mantine-color-scheme="dark"] .clearButton:hover {
background-color: rgba(255, 255, 255, 0.1);
background-color: var(--c-hover);
}
@@ -1,9 +1,9 @@
/* Tooltip Container */
.tooltip-container {
position: fixed;
border: 0.0625rem solid var(--border-default);
border: 0.0625rem solid var(--c-border);
border-radius: 0.75rem;
background-color: var(--bg-raised);
background-color: var(--c-surface-raised);
box-shadow:
0 0.625rem 0.9375rem -0.1875rem rgba(0, 0, 0, 0.1),
0 0.25rem 0.375rem -0.125rem rgba(0, 0, 0, 0.05);
@@ -15,25 +15,25 @@
transform 100ms ease-out;
max-width: 50vh;
max-height: 80vh;
color: var(--text-primary);
color: var(--c-text);
display: flex;
flex-direction: column;
}
/* Pinned tooltip indicator */
.tooltip-container.pinned {
border-color: var(--primary-color, #3b82f6);
border-color: var(--primary-color, var(--c-primary));
box-shadow:
0 0.625rem 0.9375rem -0.1875rem rgba(0, 0, 0, 0.1),
0 0.25rem 0.375rem -0.125rem rgba(0, 0, 0, 0.05),
0 0 0 0.125rem rgba(59, 130, 246, 0.1);
0 0 0 0.125rem color-mix(in srgb, var(--c-primary) 10%, transparent);
}
/* Pinned tooltip header */
.tooltip-container.pinned .tooltip-header {
background-color: var(--primary-color, #3b82f6);
background-color: var(--primary-color, var(--c-primary));
color: white;
border-color: var(--primary-color, #3b82f6);
border-color: var(--primary-color, var(--c-primary));
}
/* Close button */
@@ -42,10 +42,10 @@
top: 0.5rem;
right: 0.5rem;
font-size: 0.875rem;
background: var(--bg-raised);
background: var(--c-surface-raised);
padding: 0.25rem;
border-radius: 0.25rem;
border: 0.0625rem solid var(--border-default);
border: 0.0625rem solid var(--c-border);
cursor: pointer;
transition:
background-color 0.2s ease,
@@ -64,15 +64,15 @@
}
.tooltip-pin-button:hover {
background-color: #ef4444 !important;
border-color: #ef4444 !important;
background-color: var(--c-danger) !important;
border-color: var(--c-danger) !important;
}
.tooltip-pin-button:focus,
.tooltip-pin-button:focus-visible {
outline: none;
border-color: var(--border-default) !important;
background-color: var(--bg-raised) !important;
border-color: var(--c-border) !important;
background-color: var(--c-surface-raised) !important;
}
/* Tooltip Header */
@@ -107,7 +107,7 @@
/* Tooltip Body */
.tooltip-body {
padding: 1rem;
color: var(--text-primary) !important;
color: var(--c-text) !important;
font-size: 0.875rem !important;
line-height: 1.6 !important;
overflow-y: auto;
@@ -116,47 +116,49 @@
}
.tooltip-body * {
color: var(--text-primary) !important;
color: var(--c-text) !important;
}
/* Link styling within tooltips */
.tooltip-body a {
color: var(--link-color, #3b82f6) !important;
color: var(--link-color, var(--c-primary)) !important;
text-decoration: underline;
text-decoration-color: var(--link-underline-color, rgba(59, 130, 246, 0.3));
text-decoration-color: var(
--link-underline-color,
color-mix(in srgb, var(--c-primary) 30%, transparent)
);
transition:
color 0.2s ease,
text-decoration-color 0.2s ease;
}
.tooltip-body a:hover {
color: var(--link-hover-color, #2563eb) !important;
color: var(--link-hover-color, var(--c-primary-hover)) !important;
text-decoration-color: var(
--link-hover-underline-color,
rgba(37, 99, 235, 0.5)
color-mix(in srgb, var(--c-primary-hover) 50%, transparent)
);
}
.tooltip-container .tooltip-body {
color: var(--text-primary) !important;
}
.tooltip-container .tooltip-body,
.tooltip-container .tooltip-body * {
color: var(--text-primary) !important;
color: var(--c-text) !important;
}
/* Ensure links maintain their styling */
.tooltip-container .tooltip-body a {
color: var(--link-color, #3b82f6) !important;
color: var(--link-color, var(--c-primary)) !important;
text-decoration: underline;
text-decoration-color: var(--link-underline-color, rgba(59, 130, 246, 0.3));
text-decoration-color: var(
--link-underline-color,
color-mix(in srgb, var(--c-primary) 30%, transparent)
);
}
.tooltip-container .tooltip-body a:hover {
color: var(--link-hover-color, #2563eb) !important;
color: var(--link-hover-color, var(--c-primary-hover)) !important;
text-decoration-color: var(
--link-hover-underline-color,
rgba(37, 99, 235, 0.5)
color-mix(in srgb, var(--c-primary-hover) 50%, transparent)
);
}
@@ -165,8 +167,8 @@
position: absolute;
width: 0.5rem;
height: 0.5rem;
background: var(--bg-raised);
border: 0.0625rem solid var(--border-default);
background: var(--c-surface-raised);
border: 0.0625rem solid var(--c-border);
transform: rotate(45deg);
}
@@ -17,13 +17,13 @@ export const TooltipContent: React.FC<TooltipContentProps> = ({
<div
className={`${styles["tooltip-body"]}`}
style={{
color: "var(--text-primary)",
color: "var(--c-text)",
padding: `16px ${16 + extraRightPadding}px 16px 16px`,
fontSize: "14px",
lineHeight: "1.6",
}}
>
<div style={{ color: "var(--text-primary)" }}>
<div style={{ color: "var(--c-text)" }}>
{tips ? (
<>
{tips.map((tip, index) => (
@@ -35,8 +35,8 @@ export const TooltipContent: React.FC<TooltipContentProps> = ({
<div
style={{
display: "inline-block",
backgroundColor: "var(--tooltip-title-bg)",
color: "var(--tooltip-title-color)",
backgroundColor: "var(--c-primary-tint)",
color: "var(--c-text)",
padding: "6px 12px",
borderRadius: "16px",
fontSize: "12px",
@@ -51,7 +51,7 @@ export const TooltipContent: React.FC<TooltipContentProps> = ({
<p
style={{
margin: "0 0 12px 0",
color: "var(--text-secondary)",
color: "var(--c-text-muted)",
fontSize: "13px",
}}
dangerouslySetInnerHTML={{ __html: tip.description }}
@@ -62,7 +62,7 @@ export const TooltipContent: React.FC<TooltipContentProps> = ({
style={{
margin: "0",
paddingLeft: "16px",
color: "var(--text-secondary)",
color: "var(--c-text-muted)",
fontSize: "13px",
}}
>
@@ -75,26 +75,26 @@
/* Toast Alert Type Colors */
.toast-item--success {
background: var(--color-green-100);
color: var(--text-primary);
color: var(--c-text);
border: 1px solid var(--color-green-400);
}
.toast-item--error {
background: var(--color-red-100);
color: var(--text-primary);
color: var(--c-text);
border: 1px solid var(--color-red-400);
}
.toast-item--warning {
background: var(--color-yellow-100);
color: var(--text-primary);
color: var(--c-text);
border: 1px solid var(--color-yellow-400);
}
.toast-item--neutral {
background: var(--bg-surface);
color: var(--text-primary);
border: 1px solid var(--border-default);
background: var(--c-surface);
color: var(--c-text);
border: 1px solid var(--c-border);
}
/* Toast Header Row */
@@ -146,7 +146,7 @@
border-radius: 999px;
border: none;
background: transparent;
color: var(--text-secondary);
color: var(--c-text-muted);
cursor: pointer;
display: flex;
align-items: center;
@@ -166,7 +166,7 @@
.toast-progress-container {
margin-top: 8px;
height: 6px;
background: var(--bg-muted);
background: var(--c-surface-sunken);
border-radius: 999px;
overflow: hidden;
}
@@ -217,21 +217,21 @@
}
.toast-action-button--success {
color: var(--text-primary);
color: var(--c-text);
border-color: var(--color-green-400);
}
.toast-action-button--error {
color: var(--text-primary);
color: var(--c-text);
border-color: var(--color-red-400);
}
.toast-action-button--warning {
color: var(--text-primary);
color: var(--c-text);
border-color: var(--color-yellow-400);
}
.toast-action-button--neutral {
color: var(--text-primary);
border-color: var(--border-default);
color: var(--c-text);
border-color: var(--c-border);
}
@@ -168,7 +168,7 @@ export default function RightSidebar() {
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 ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--c-bg-raised)] border-l border-[var(--c-border-subtle)] transition-all duration-300 ease-out ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
style={{
width: computedWidth(),
padding: "0",
@@ -4,8 +4,8 @@
align-items: center;
gap: 2px;
padding: 4px 8px;
border-bottom: 1px solid var(--border-subtle);
background-color: var(--bg-toolbar);
border-bottom: 1px solid var(--c-border-subtle);
background-color: var(--c-bg-raised);
flex-shrink: 0;
}
@@ -13,115 +13,110 @@
.tool-panel__fullscreen-surface-inner {
--fullscreen-bg-surface-1: color-mix(
in srgb,
var(--bg-toolbar) 96%,
var(--c-bg-raised) 96%,
transparent
);
--fullscreen-bg-surface-2: color-mix(
in srgb,
var(--bg-background) 90%,
transparent
);
--fullscreen-bg-header: var(--bg-toolbar);
--fullscreen-bg-controls-1: var(--bg-toolbar);
--fullscreen-bg-surface-2: color-mix(in srgb, var(--c-bg) 90%, transparent);
--fullscreen-bg-header: var(--c-bg-raised);
--fullscreen-bg-controls-1: var(--c-bg-raised);
--fullscreen-bg-controls-2: color-mix(
in srgb,
var(--bg-toolbar) 95%,
var(--bg-background)
);
--fullscreen-bg-body-1: color-mix(
in srgb,
var(--bg-background) 86%,
transparent
var(--c-bg-raised) 95%,
var(--c-bg)
);
--fullscreen-bg-body-1: color-mix(in srgb, var(--c-bg) 86%, transparent);
--fullscreen-bg-body-2: color-mix(
in srgb,
var(--bg-toolbar) 78%,
var(--c-bg-raised) 78%,
transparent
);
--fullscreen-bg-group: color-mix(in srgb, var(--bg-toolbar) 82%, transparent);
--fullscreen-bg-item: color-mix(in srgb, var(--bg-toolbar) 88%, transparent);
--fullscreen-bg-group: color-mix(
in srgb,
var(--c-bg-raised) 82%,
transparent
);
--fullscreen-bg-item: color-mix(in srgb, var(--c-bg-raised) 88%, transparent);
--fullscreen-bg-list-item: color-mix(
in srgb,
var(--bg-toolbar) 86%,
var(--c-bg-raised) 86%,
transparent
);
--fullscreen-bg-icon-detailed: color-mix(
in srgb,
var(--bg-muted) 75%,
var(--c-surface-sunken) 75%,
transparent
);
--fullscreen-bg-icon-compact: color-mix(
in srgb,
var(--bg-muted) 70%,
var(--c-surface-sunken) 70%,
transparent
);
--fullscreen-border-subtle-75: color-mix(
in srgb,
var(--border-subtle) 75%,
var(--c-border-subtle) 75%,
transparent
);
--fullscreen-border-subtle-70: color-mix(
in srgb,
var(--border-subtle) 70%,
var(--c-border-subtle) 70%,
transparent
);
--fullscreen-border-subtle-65: color-mix(
in srgb,
var(--border-subtle) 65%,
var(--c-border-subtle) 65%,
transparent
);
--fullscreen-border-favorites: color-mix(
in srgb,
var(--special-color-favorites) 25%,
var(--border-subtle)
var(--c-border-subtle)
);
--fullscreen-border-recommended: color-mix(
in srgb,
var(--special-color-recommended) 25%,
var(--border-subtle)
var(--c-border-subtle)
);
--fullscreen-shadow-primary: color-mix(
in srgb,
var(--shadow-color, rgba(15, 23, 42, 0.55)) 25%,
var(--shadow-color, color-mix(in srgb, var(--p-gray-900) 55%, transparent))
25%,
transparent
);
--fullscreen-shadow-secondary: color-mix(
in srgb,
var(--shadow-color, rgba(15, 23, 42, 0.35)) 30%,
var(--shadow-color, color-mix(in srgb, var(--p-gray-900) 35%, transparent))
30%,
transparent
);
--fullscreen-shadow-group: color-mix(
in srgb,
var(--shadow-color, rgba(15, 23, 42, 0.45)) 18%,
var(--shadow-color, color-mix(in srgb, var(--p-gray-900) 45%, transparent))
18%,
transparent
);
--fullscreen-accent-hover: color-mix(
in srgb,
var(--text-primary) 20%,
var(--border-subtle)
var(--c-text) 20%,
var(--c-border-subtle)
);
--fullscreen-accent-selected: color-mix(
in srgb,
var(--text-primary) 30%,
var(--border-subtle)
);
--fullscreen-accent-ring: color-mix(
in srgb,
var(--text-primary) 15%,
transparent
var(--c-text) 30%,
var(--c-border-subtle)
);
--fullscreen-accent-ring: color-mix(in srgb, var(--c-text) 15%, transparent);
--fullscreen-accent-list-bg: color-mix(
in srgb,
var(--text-primary) 8%,
var(--bg-toolbar)
var(--c-text) 8%,
var(--c-bg-raised)
);
--fullscreen-accent-list-border: color-mix(
in srgb,
var(--text-primary) 20%,
var(--border-subtle)
var(--c-text) 20%,
var(--c-border-subtle)
);
--fullscreen-text-icon: var(--text-primary);
--fullscreen-text-icon-compact: var(--text-primary);
--fullscreen-text-icon: var(--c-text);
--fullscreen-text-icon-compact: var(--c-text);
}
.tool-panel {
@@ -153,7 +148,7 @@
.tool-panel__collapsed-divider {
height: 1px;
background: var(--border-subtle);
background: var(--c-border-subtle);
margin: 0 0.5rem 8px;
}
@@ -178,7 +173,7 @@
border: 1px solid transparent;
border-radius: 0.5rem;
background: transparent;
color: var(--tools-text-and-icon-color);
color: var(--c-text);
cursor: pointer;
transition:
background 120ms ease-out,
@@ -198,8 +193,8 @@
.tool-panel__expand-btn {
flex-shrink: 0;
color: var(--text-secondary) !important;
border-color: var(--border-subtle) !important;
color: var(--c-text-muted) !important;
border-color: var(--c-border-subtle) !important;
}
/* The collapse/expand toggle keeps a stable identity across the collapsed strip
@@ -210,25 +205,25 @@
}
.tool-panel__expand-btn svg {
color: var(--text-secondary) !important;
color: var(--c-text-muted) !important;
}
.tool-panel__expand-btn:hover {
color: var(--text-primary) !important;
color: var(--c-text) !important;
border-color: var(--border) !important;
}
.tool-panel__collapsed-search-btn {
flex-shrink: 0;
color: var(--text-secondary) !important;
color: var(--c-text-muted) !important;
}
.tool-panel__collapsed-search-btn svg {
color: var(--text-secondary) !important;
color: var(--c-text-muted) !important;
}
.tool-panel__collapsed-search-btn:hover {
color: var(--text-primary) !important;
color: var(--c-text) !important;
}
.tool-panel__back-btn {
@@ -237,7 +232,7 @@
.tool-panel__back-bar {
flex-shrink: 0;
border-bottom: 1px solid var(--border-subtle) !important;
border-bottom: 1px solid var(--c-border-subtle) !important;
}
.tool-panel--fullscreen-active {
@@ -282,7 +277,7 @@
/* Search that separates the Policies section from the Tools list below it. */
.tool-panel__between-search {
padding: 0.5rem 0.75rem 0.25rem;
border-top: 1px solid var(--border-subtle, var(--color-border));
border-top: 1px solid var(--c-border-subtle, var(--c-border));
}
.tool-panel__between-search .search-input-container {
@@ -292,7 +287,7 @@
/* Slightly recessed search field so it reads as distinct from the panel. */
.tool-panel__between-search input {
background-color: var(--bg-muted);
background-color: var(--c-surface-sunken);
}
::view-transition-old(tool-rail) {
@@ -334,7 +329,7 @@
}
.tool-panel--fullscreen {
background: var(--bg-toolbar);
background: var(--c-bg-raised);
}
.tool-panel__placeholder {
@@ -342,7 +337,7 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
color: var(--c-text-subtle);
font-size: 0.9rem;
padding: 1.5rem;
text-align: center;
@@ -403,8 +398,8 @@
align-items: center;
gap: 1rem;
padding: 0.75rem 1.75rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
border-bottom: 1px solid var(--c-border-subtle);
background: var(--c-bg-raised);
}
.tool-panel__fullscreen-brand {
@@ -428,8 +423,8 @@
align-items: center;
gap: 1rem;
padding: 0.75rem 1.75rem;
border-bottom: 1px solid var(--tool-panel-search-border-bottom);
background: var(--tool-panel-search-bg);
border-bottom: 1px solid var(--c-border);
background: var(--c-surface-sunken);
}
.tool-panel__fullscreen-controls .search-input-container {
@@ -546,7 +541,10 @@
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"]):not(:disabled) {
transform: translateY(-2px);
border-color: var(--fullscreen-accent-hover);
box-shadow: var(--shadow-xl, 0 18px 34px rgba(15, 23, 42, 0.14));
box-shadow: var(
--shadow-xl,
0 18px 34px color-mix(in srgb, var(--p-gray-900) 14%, transparent)
);
}
.tool-panel__fullscreen-item--selected {
@@ -619,8 +617,8 @@
inset: 0;
background: linear-gradient(
135deg,
color-mix(in srgb, var(--text-primary) 12%, transparent),
color-mix(in srgb, var(--text-primary) 4%, transparent)
color-mix(in srgb, var(--c-text) 12%, transparent),
color-mix(in srgb, var(--c-text) 4%, transparent)
);
opacity: 0;
transition: opacity 0.2s ease;
@@ -630,8 +628,7 @@
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"])
.tool-panel__fullscreen-icon {
transform: scale(1.08);
box-shadow: 0 4px 12px
color-mix(in srgb, var(--text-primary) 15%, transparent);
box-shadow: 0 4px 12px color-mix(in srgb, var(--c-text) 15%, transparent);
}
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"])
@@ -656,7 +653,7 @@
}
.tool-panel__fullscreen-name {
color: var(--text-primary);
color: var(--c-text);
font-size: 13px !important;
font-weight: 500 !important;
}
@@ -753,8 +750,8 @@
inset: 0;
background: linear-gradient(
135deg,
color-mix(in srgb, var(--text-primary) 10%, transparent),
color-mix(in srgb, var(--text-primary) 3%, transparent)
color-mix(in srgb, var(--c-text) 10%, transparent),
color-mix(in srgb, var(--c-text) 3%, transparent)
);
opacity: 0;
transition: opacity 0.2s ease;
@@ -764,7 +761,7 @@
.tool-panel__fullscreen-list-item:hover:not([aria-disabled="true"])
.tool-panel__fullscreen-list-icon {
transform: scale(1.06);
box-shadow: 0 2px 8px color-mix(in srgb, var(--text-primary) 12%, transparent);
box-shadow: 0 2px 8px color-mix(in srgb, var(--c-text) 12%, transparent);
}
.tool-panel__fullscreen-list-item:hover:not([aria-disabled="true"])
@@ -1,10 +1,14 @@
.tool-panel-mode-prompt__modal {
background: color-mix(in srgb, var(--bg-toolbar) 94%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
background: color-mix(in srgb, var(--c-bg-raised) 94%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 70%, transparent);
box-shadow: 0 32px 64px
color-mix(
in srgb,
var(--shadow-color, rgba(15, 23, 42, 0.55)) 20%,
var(
--shadow-color,
color-mix(in srgb, var(--p-gray-900) 55%, transparent)
)
20%,
transparent
);
max-width: min(46rem, 100%);
@@ -22,8 +26,8 @@
gap: 1rem;
background: linear-gradient(
145deg,
color-mix(in srgb, var(--bg-surface) 96%, transparent),
color-mix(in srgb, var(--bg-muted) 70%, transparent)
color-mix(in srgb, var(--c-surface) 96%, transparent),
color-mix(in srgb, var(--c-surface-sunken) 70%, transparent)
);
width: 100%;
max-width: 19rem;
@@ -31,28 +35,20 @@
.tool-panel-mode-prompt__card--sidebar {
border: 1px solid
color-mix(
in srgb,
var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 18%,
var(--border-subtle)
);
color-mix(in srgb, var(--c-primary) 18%, var(--c-border-subtle));
background: linear-gradient(
165deg,
color-mix(in srgb, var(--bg-surface) 96%, transparent),
color-mix(
in srgb,
var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 8%,
transparent
)
color-mix(in srgb, var(--c-surface) 96%, transparent),
color-mix(in srgb, var(--c-primary) 8%, transparent)
);
}
.tool-panel-mode-prompt__preview {
border-radius: 0.9rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 70%, transparent);
background: linear-gradient(
135deg,
color-mix(in srgb, var(--bg-muted) 82%, transparent),
color-mix(in srgb, var(--c-surface-sunken) 82%, transparent),
transparent 75%
);
padding: 0.75rem;
@@ -76,54 +72,49 @@
gap: 0.45rem;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bg-muted) 88%, transparent),
color-mix(in srgb, var(--bg-muted) 72%, transparent)
color-mix(in srgb, var(--c-surface-sunken) 88%, transparent),
color-mix(in srgb, var(--c-surface-sunken) 72%, transparent)
);
border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 65%, transparent);
}
.tool-panel-mode-prompt__sidebar-search {
height: 0.5rem;
border-radius: 0.4rem;
background: color-mix(in srgb, var(--bg-background) 90%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 60%, transparent);
background: color-mix(in srgb, var(--c-bg) 90%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 60%, transparent);
}
.tool-panel-mode-prompt__sidebar-item {
height: 0.55rem;
border-radius: 0.35rem;
background: color-mix(
in srgb,
var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 32%,
var(--bg-muted)
);
background: color-mix(in srgb, var(--c-primary) 32%, var(--c-surface-sunken));
}
.tool-panel-mode-prompt__sidebar-item--muted {
background: color-mix(in srgb, var(--bg-background) 88%, transparent);
background: color-mix(in srgb, var(--c-bg) 88%, transparent);
}
.tool-panel-mode-prompt__workspace {
flex: 1;
border-radius: 0.65rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 65%, transparent);
padding: 0.5rem;
display: grid;
gap: 0.35rem;
grid-template-rows: 1.4fr 0.6fr;
background: linear-gradient(
160deg,
color-mix(in srgb, var(--bg-background) 94%, transparent),
color-mix(in srgb, var(--bg-muted) 68%, transparent)
color-mix(in srgb, var(--c-bg) 94%, transparent),
color-mix(in srgb, var(--c-surface-sunken) 68%, transparent)
);
}
.tool-panel-mode-prompt__workspace-page {
border-radius: 0.45rem;
background: color-mix(in srgb, var(--bg-surface) 96%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent);
box-shadow: inset 0 0 0 1px
color-mix(in srgb, var(--bg-background) 60%, transparent);
background: color-mix(in srgb, var(--c-surface) 96%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 55%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-bg) 60%, transparent);
}
.tool-panel-mode-prompt__workspace-page--secondary {
@@ -151,11 +142,11 @@
.tool-panel-mode-prompt__legacy-card {
border-radius: 0.45rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 55%, transparent);
background: linear-gradient(
150deg,
color-mix(in srgb, var(--bg-muted) 88%, transparent),
color-mix(in srgb, var(--bg-background) 76%, transparent)
color-mix(in srgb, var(--c-surface-sunken) 88%, transparent),
color-mix(in srgb, var(--c-bg) 76%, transparent)
);
height: 1.2rem;
}
@@ -185,11 +176,11 @@
.tool-panel-mode-prompt__fullscreen-card {
border-radius: 0.45rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent);
border: 1px solid color-mix(in srgb, var(--c-border-subtle) 55%, transparent);
background: linear-gradient(
150deg,
color-mix(in srgb, var(--bg-muted) 88%, transparent),
color-mix(in srgb, var(--bg-background) 76%, transparent)
color-mix(in srgb, var(--c-surface-sunken) 88%, transparent),
color-mix(in srgb, var(--c-bg) 76%, transparent)
);
height: 1.2rem;
}
@@ -210,20 +201,15 @@
}
.tool-panel-mode-prompt__action:hover {
box-shadow: 0 10px 18px
color-mix(
in srgb,
var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 25%,
transparent
);
box-shadow: 0 10px 18px color-mix(in srgb, var(--c-primary) 25%, transparent);
}
.tool-panel-mode-prompt__maybe-later {
color: color-mix(in srgb, var(--text-secondary) 90%, var(--text-muted));
color: color-mix(in srgb, var(--c-text-muted) 90%, var(--c-text-subtle));
}
.tool-panel-mode-prompt__maybe-later:hover {
background: color-mix(in srgb, var(--bg-muted) 78%, transparent);
background: color-mix(in srgb, var(--c-surface-sunken) 78%, transparent);
}
@media (max-width: 600px) {
@@ -37,7 +37,7 @@ const HEADER_TEXT_STYLE: React.CSSProperties = {
padding: "0.25rem 0 0.35rem 0.5rem",
textTransform: "uppercase",
letterSpacing: "0.06em",
color: "var(--text-muted)",
color: "var(--c-text-subtle)",
};
const SCROLLABLE_STYLE: React.CSSProperties = {
flex: 1,
@@ -50,7 +50,7 @@ const SCROLLABLE_STYLE: React.CSSProperties = {
const CONTAINER_STYLE: React.CSSProperties = {
display: "flex",
flexDirection: "column",
background: "var(--bg-toolbar)",
background: "var(--c-bg-raised)",
};
const toTitleCase = (s: string) =>
s.replace(
@@ -16,7 +16,7 @@
}
.containerBorder {
border: 1px solid var(--border-default, #333);
border: 1px solid var(--c-border);
}
/* Page thumbnail styles */
@@ -105,8 +105,8 @@
.gridTileSelected,
.gridTileHovered {
border: 2px solid var(--mantine-primary-color-filled, #3b82f6);
background-color: rgba(59, 130, 246, 0.2);
border: 2px solid var(--c-primary);
background-color: color-mix(in srgb, var(--c-primary) 20%, transparent);
}
/* Preview header */
@@ -116,14 +116,14 @@
.divider {
height: 1px;
background-color: var(--border-default, #333);
background-color: var(--c-border);
margin-bottom: 8px;
}
.previewLabel {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
color: var(--c-text);
text-align: center;
}
@@ -220,7 +220,7 @@ export default function PageNumberPreview({
width: "100%",
aspectRatio: `${(pageSize?.widthPts ?? 595.28) / (pageSize?.heightPts ?? 841.89)} / 1`,
backgroundColor: pageThumbnail ? "white" : "rgba(255,255,255,0.03)",
border: "1px solid var(--border-default, #333)",
border: "1px solid var(--c-border, #333)",
overflow: "hidden" as const,
}),
[pageSize, pageThumbnail],
@@ -16,7 +16,7 @@
}
.containerBorder {
border: 1px solid var(--border-default, #333);
border: 1px solid var(--c-border);
}
/* Page thumbnail styles */
@@ -95,7 +95,7 @@
.gridTileSelected,
.gridTileHovered {
border: 2px solid var(--mantine-primary-color-filled, #3b82f6);
border: 2px solid var(--c-primary);
}
/* Preview header */
@@ -105,14 +105,14 @@
.divider {
height: 1px;
background-color: var(--border-default, #333);
background-color: var(--c-border);
margin-bottom: 8px;
}
.previewLabel {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
color: var(--c-text);
text-align: center;
}
@@ -127,7 +127,7 @@
/* Information text container */
.informationContainer {
background-color: var(--information-text-bg);
background-color: var(--c-surface);
padding: 2px;
padding-left: 8px;
padding-right: 8px;
@@ -280,7 +280,7 @@ export function computeStampPreviewStyle(
width: "100%",
aspectRatio: `${(pageSize?.widthPts ?? 595.28) / (pageSize?.heightPts ?? 841.89)} / 1`,
backgroundColor: hasPageThumbnail ? "white" : "rgba(255,255,255,0.03)",
border: "1px solid var(--border-default, #333)",
border: "1px solid var(--c-border, #333)",
overflow: "hidden",
},
item: {
@@ -191,11 +191,9 @@ export default function AutomationEntry({
className="tool-button"
style={{
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
color: "var(--c-text)",
overflow: "visible",
backgroundColor: shouldShowMenu
? "var(--automation-entry-hover-bg)"
: undefined,
backgroundColor: shouldShowMenu ? "var(--c-hover)" : undefined,
}}
>
{buttonContent}
@@ -153,7 +153,7 @@ export default function AutomationRun({
style={{
width: 16,
height: 16,
border: "2px solid #ccc",
border: "2px solid var(--c-border)",
borderRadius: "50%",
}}
/>
@@ -148,7 +148,7 @@ export default function ToolList({
borderTop: "none",
borderRadius:
"0 0 var(--mantine-radius-lg) var(--mantine-radius-lg)",
backgroundColor: "var(--active-bg)",
backgroundColor: "var(--c-active)",
padding: "var(--mantine-spacing-xs)",
}}
>
@@ -75,7 +75,7 @@ const CertificateTypeSettings = ({
if (!hasAlternativeSources) {
return (
<Stack gap="md">
<div style={{ color: "var(--text-muted)", fontSize: "11px" }}>
<div style={{ color: "var(--c-text-subtle)", fontSize: "11px" }}>
{t(
"certSign.source.noOtherSources",
"No other certificate sources are available.",
@@ -317,9 +317,9 @@ const SignRequestPanel = ({ data }: SignRequestPanelProps) => {
onClick={handleAddToActiveFiles}
fullWidth
style={{
backgroundColor: "var(--landing-inner-paper-bg)",
color: "var(--btn-open-file)",
border: "1px solid var(--landing-inner-paper-border)",
backgroundColor: "var(--c-surface-raised)",
color: "var(--c-primary)",
border: "1px solid var(--c-border)",
}}
>
{t("certSign.collab.signRequest.addToFiles", "Add to Active Files")}

Some files were not shown because too many files have changed in this diff Show More