redesign(p0): land Dawn dark-theme foundation with one-shot migration and edge-to-edge polish
This commit is contained in:
parent
0404965f44
commit
b41fbfabec
12 changed files with 201 additions and 179 deletions
|
|
@ -4,6 +4,8 @@ import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.activity.EdgeToEdge;
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.core.view.WindowCompat;
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat;
|
||||||
import com.getcapacitor.BridgeActivity;
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {
|
public class MainActivity extends BridgeActivity {
|
||||||
|
|
@ -40,6 +42,14 @@ public class MainActivity extends BridgeActivity {
|
||||||
registerPlugin(CallForegroundPlugin.class);
|
registerPlugin(CallForegroundPlugin.class);
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
// Force light icons on both system bars: our CSS is permanently dark
|
||||||
|
// (Dawn redesign), but EdgeToEdge.enable auto-detects icon tint from
|
||||||
|
// the device uiMode — on a light-mode device that gives dark icons
|
||||||
|
// over our dark bars and they vanish.
|
||||||
|
WindowInsetsControllerCompat controller =
|
||||||
|
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
|
||||||
|
controller.setAppearanceLightStatusBars(false);
|
||||||
|
controller.setAppearanceLightNavigationBars(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
||||||
"@capacitor/preferences": "8.0.1",
|
"@capacitor/preferences": "8.0.1",
|
||||||
"@capacitor/push-notifications": "8.0.3",
|
"@capacitor/push-notifications": "8.0.3",
|
||||||
"@capacitor/toast": "8.0.1",
|
"@capacitor/toast": "8.0.1",
|
||||||
|
"@fontsource-variable/jetbrains-mono": "5.2.5",
|
||||||
"@fontsource/inter": "4.5.14",
|
"@fontsource/inter": "4.5.14",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
|
|
@ -2427,6 +2428,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fontsource-variable/jetbrains-mono": {
|
||||||
|
"version": "5.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.5.tgz",
|
||||||
|
"integrity": "sha512-G3sN1xq1moZd0JL+hFaA4MEdsiQS+JXC/z7m+EqA5/Fzn5CQlXGUaaNKFGQdDsFuLTnCfW0KOOSWHjygNfjEPw==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fontsource/inter": {
|
"node_modules/@fontsource/inter": {
|
||||||
"version": "4.5.14",
|
"version": "4.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.14.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.14.tgz",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@
|
||||||
"@capacitor/preferences": "8.0.1",
|
"@capacitor/preferences": "8.0.1",
|
||||||
"@capacitor/push-notifications": "8.0.3",
|
"@capacitor/push-notifications": "8.0.3",
|
||||||
"@capacitor/toast": "8.0.1",
|
"@capacitor/toast": "8.0.1",
|
||||||
|
"@fontsource-variable/jetbrains-mono": "5.2.5",
|
||||||
"@fontsource/inter": "4.5.14",
|
"@fontsource/inter": "4.5.14",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
|
|
|
||||||
|
|
@ -36,111 +36,21 @@ import { DateFormat, MessageLayout, MessageSpacing, settingsAtom } from '../../.
|
||||||
import { SettingTile } from '../../../components/setting-tile';
|
import { SettingTile } from '../../../components/setting-tile';
|
||||||
import { KeySymbol } from '../../../utils/key-symbol';
|
import { KeySymbol } from '../../../utils/key-symbol';
|
||||||
import { isMacOS } from '../../../utils/user-agent';
|
import { isMacOS } from '../../../utils/user-agent';
|
||||||
import {
|
|
||||||
DarkTheme,
|
|
||||||
LightTheme,
|
|
||||||
useThemes,
|
|
||||||
} from '../../../hooks/useTheme';
|
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
|
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
|
||||||
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
||||||
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
import { SequenceCardStyle } from '../styles.css';
|
||||||
|
|
||||||
const SYSTEM_THEME_ID = 'system';
|
|
||||||
|
|
||||||
const THEME_I18N_KEYS: Record<string, string> = {
|
|
||||||
[LightTheme.id]: 'Settings.theme_light',
|
|
||||||
[DarkTheme.id]: 'Settings.theme_dark',
|
|
||||||
};
|
|
||||||
|
|
||||||
function ThemeSelect() {
|
function ThemeSelect() {
|
||||||
|
// Theme switching is locked to dark while the Dawn redesign rolls out — Vojo
|
||||||
|
// light is a separate plan. Kept as a read-only label so the existing
|
||||||
|
// SettingTile layout in the Appearance section still renders.
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const themes = useThemes();
|
|
||||||
const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme');
|
|
||||||
const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId');
|
|
||||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
|
||||||
|
|
||||||
const themeName = (id: string) => t(THEME_I18N_KEYS[id] ?? id);
|
|
||||||
|
|
||||||
const currentLabel = systemTheme
|
|
||||||
? t('Settings.system_theme')
|
|
||||||
: themeName(themeId ?? LightTheme.id);
|
|
||||||
|
|
||||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
|
||||||
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (id: string) => {
|
|
||||||
if (id === SYSTEM_THEME_ID) {
|
|
||||||
setSystemTheme(true);
|
|
||||||
} else {
|
|
||||||
setSystemTheme(false);
|
|
||||||
setThemeId(id);
|
|
||||||
}
|
|
||||||
setMenuCords(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedId = systemTheme ? SYSTEM_THEME_ID : (themeId ?? LightTheme.id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Text size="T300" priority="300">
|
||||||
<Button
|
{t('Settings.theme_dark')}
|
||||||
size="300"
|
</Text>
|
||||||
variant="Primary"
|
|
||||||
outlined
|
|
||||||
fill="Soft"
|
|
||||||
radii="300"
|
|
||||||
after={<Icon size="300" src={Icons.ChevronBottom} />}
|
|
||||||
onClick={handleOpenMenu}
|
|
||||||
>
|
|
||||||
<Text size="T300">{currentLabel}</Text>
|
|
||||||
</Button>
|
|
||||||
<PopOut
|
|
||||||
anchor={menuCords}
|
|
||||||
offset={5}
|
|
||||||
position="Bottom"
|
|
||||||
align="End"
|
|
||||||
content={
|
|
||||||
<FocusTrap
|
|
||||||
focusTrapOptions={{
|
|
||||||
initialFocus: false,
|
|
||||||
onDeactivate: () => setMenuCords(undefined),
|
|
||||||
clickOutsideDeactivates: true,
|
|
||||||
isKeyForward: (evt: KeyboardEvent) =>
|
|
||||||
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
|
||||||
isKeyBackward: (evt: KeyboardEvent) =>
|
|
||||||
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
|
||||||
escapeDeactivates: stopPropagation,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu>
|
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
|
||||||
<MenuItem
|
|
||||||
size="300"
|
|
||||||
variant={selectedId === SYSTEM_THEME_ID ? 'Primary' : 'Surface'}
|
|
||||||
radii="300"
|
|
||||||
onClick={() => handleSelect(SYSTEM_THEME_ID)}
|
|
||||||
>
|
|
||||||
<Text size="T300">{t('Settings.system_theme')}</Text>
|
|
||||||
</MenuItem>
|
|
||||||
{themes.map((theme) => (
|
|
||||||
<MenuItem
|
|
||||||
key={theme.id}
|
|
||||||
size="300"
|
|
||||||
variant={selectedId === theme.id ? 'Primary' : 'Surface'}
|
|
||||||
radii="300"
|
|
||||||
onClick={() => handleSelect(theme.id)}
|
|
||||||
>
|
|
||||||
<Text size="T300">{themeName(theme.id)}</Text>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Menu>
|
|
||||||
</FocusTrap>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,11 @@ export function WelcomePage() {
|
||||||
<PageHero
|
<PageHero
|
||||||
icon={<img width="70" height="70" src={VojoSVG} alt="Vojo Logo" />}
|
icon={<img width="70" height="70" src={VojoSVG} alt="Vojo Logo" />}
|
||||||
title="Welcome to Vojo"
|
title="Welcome to Vojo"
|
||||||
subTitle={<span>{__APP_VERSION__}</span>}
|
subTitle={
|
||||||
|
<span style={{ fontFamily: '"JetBrains Mono Variable", ui-monospace, monospace' }}>
|
||||||
|
{__APP_VERSION__}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</PageHeroSection>
|
</PageHeroSection>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParams
|
||||||
import { mDirectAtom } from '../../../state/mDirectList';
|
import { mDirectAtom } from '../../../state/mDirectList';
|
||||||
import { getDirectRoomPath } from '../../pathUtils';
|
import { getDirectRoomPath } from '../../pathUtils';
|
||||||
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
||||||
|
import { isDirectInvite } from '../../../utils/room';
|
||||||
|
|
||||||
export function HomeRouteRoomProvider({ children }: { children: ReactNode }) {
|
export function HomeRouteRoomProvider({ children }: { children: ReactNode }) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
|
@ -21,7 +22,18 @@ export function HomeRouteRoomProvider({ children }: { children: ReactNode }) {
|
||||||
const roomId = useSelectedRoom();
|
const roomId = useSelectedRoom();
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
|
|
||||||
if (room && mDirects.has(room.roomId)) {
|
// Cold-start push routing lands on /home/{roomId} (sw.ts has no access to
|
||||||
|
// mDirectAtom). For DM rooms we redirect to /direct/. mDirectAtom is hydrated
|
||||||
|
// from useEffect so it can still be empty on the first frame after a fresh
|
||||||
|
// invite — fall back to the synchronous SDK signals (invite-state DMInviter
|
||||||
|
// or m.room.member content with is_direct: true) so the redirect lands on
|
||||||
|
// the first paint instead of one frame later. See plan §6.5 / §6.7.
|
||||||
|
if (
|
||||||
|
room &&
|
||||||
|
(mDirects.has(room.roomId) ||
|
||||||
|
!!room.getDMInviter() ||
|
||||||
|
isDirectInvite(room, mx.getUserId()))
|
||||||
|
) {
|
||||||
const alias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
const alias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
||||||
return <Navigate to={getDirectRoomPath(alias, eventId)} replace />;
|
return <Navigate to={getDirectRoomPath(alias, eventId)} replace />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,12 @@ export interface Settings {
|
||||||
dateFormatString: string;
|
dateFormatString: string;
|
||||||
|
|
||||||
developerTools: boolean;
|
developerTools: boolean;
|
||||||
|
|
||||||
|
migrationsApplied?: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DAWN_MIGRATION_KEY = 'dawn-redesign-v1';
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
themeId: undefined,
|
themeId: undefined,
|
||||||
useSystemTheme: true,
|
useSystemTheme: true,
|
||||||
|
|
@ -77,19 +81,42 @@ const defaultSettings: Settings = {
|
||||||
developerTools: false,
|
developerTools: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = () => {
|
|
||||||
const settings = localStorage.getItem(STORAGE_KEY);
|
|
||||||
if (settings === null) return defaultSettings;
|
|
||||||
return {
|
|
||||||
...defaultSettings,
|
|
||||||
...(JSON.parse(settings) as Settings),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setSettings = (settings: Settings) => {
|
export const setSettings = (settings: Settings) => {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSettings = (): Settings => {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
|
||||||
|
let parsed: Partial<Settings> | null = null;
|
||||||
|
if (raw !== null) {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw) as Partial<Settings>;
|
||||||
|
} catch {
|
||||||
|
parsed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const merged: Settings = { ...defaultSettings, ...(parsed ?? {}) };
|
||||||
|
|
||||||
|
// One-shot Dawn redesign migration: force the dark theme on every existing
|
||||||
|
// user so they see the redesigned palette. Stamped so we run it exactly once;
|
||||||
|
// future Vojo-light themes will not be overwritten on subsequent loads.
|
||||||
|
// Synchronous — runs at settingsAtom creation, before useActiveTheme reads
|
||||||
|
// the value during the first render.
|
||||||
|
if (!merged.migrationsApplied?.[DAWN_MIGRATION_KEY]) {
|
||||||
|
merged.useSystemTheme = false;
|
||||||
|
merged.themeId = 'dark-theme';
|
||||||
|
merged.migrationsApplied = {
|
||||||
|
...(merged.migrationsApplied ?? {}),
|
||||||
|
[DAWN_MIGRATION_KEY]: true,
|
||||||
|
};
|
||||||
|
setSettings(merged);
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
};
|
||||||
|
|
||||||
const baseSettings = atom<Settings>(getSettings());
|
const baseSettings = atom<Settings>(getSettings());
|
||||||
export const settingsAtom = atom<Settings, [Settings], undefined>(
|
export const settingsAtom = atom<Settings, [Settings], undefined>(
|
||||||
(get) => get(baseSettings),
|
(get) => get(baseSettings),
|
||||||
|
|
|
||||||
27
src/app/styles/global.css.ts
Normal file
27
src/app/styles/global.css.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { globalStyle } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
// Vojo-owned safe-area background. Used by `body { background-color: ... }`
|
||||||
|
// in `src/index.css` to paint Android edge-to-edge cutout / nav-bar zones.
|
||||||
|
// Matches `Background.Container` from the Dawn palette in `src/colors.css.ts`
|
||||||
|
// (`#0d0e11`, canon DAWN.bg2). The safe-area zone reads as a continuation of
|
||||||
|
// the sidebar / nav panels — no visible color seam at the system-bar boundary.
|
||||||
|
//
|
||||||
|
// Why a custom var (and not `color.Background.Container`):
|
||||||
|
// 1. Folds emits its color tokens scoped to a theme class (e.g. `--oq6d070`
|
||||||
|
// lives inside the `.lightTheme` selector). Before useTheme appends a
|
||||||
|
// theme class to body, `var(--oq6d070)` resolves to its initial value
|
||||||
|
// and `background-color` falls back to transparent — system bars show
|
||||||
|
// through unpainted on Android edge-to-edge. A `:root`-level var here
|
||||||
|
// resolves from the very first paint.
|
||||||
|
// 2. `--oq6d070` is a folds@2.6 internal vanilla-extract debugId hash and
|
||||||
|
// will rotate on any folds upgrade.
|
||||||
|
//
|
||||||
|
// The hardcoded #0d0e11 in `index.css` is the matching fallback for the
|
||||||
|
// instant before this CSS file is parsed.
|
||||||
|
//
|
||||||
|
// See docs/plans/dm_1x1_redesign.md §6.6 / R13.
|
||||||
|
globalStyle(':root', {
|
||||||
|
vars: {
|
||||||
|
'--vojo-safe-area-bg': '#0d0e11',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,104 +1,118 @@
|
||||||
import { createTheme } from '@vanilla-extract/css';
|
import { createTheme } from '@vanilla-extract/css';
|
||||||
import { color } from 'folds';
|
import { color } from 'folds';
|
||||||
|
|
||||||
// Базовая тёмная палитра приложения
|
// Dawn / Stream-v2 palette — see docs/plans/dm_1x1_redesign.md §1, §6 and
|
||||||
const navDark = '#121314'; // левая панель (навигация)
|
// docs/design/new-direct-messages-design/project/stream-v2-dawn.jsx const DAWN.
|
||||||
const contentDark = '#0d0d0e'; // правая часть (контент/чат)
|
//
|
||||||
|
// Tier mapping vs. the canon (DAWN.{bg2, bg, surface}):
|
||||||
|
// bg2 #0d0e11 — app/window deepest level. The DM list panel AND the chat
|
||||||
|
// content area share this tone in the canon (ChatListDawn
|
||||||
|
// line 130 `<div ... background: DAWN.bg2 ...>` ===
|
||||||
|
// DawnDesktopV3 line 220 `<WindowFrame bg={DAWN.bg2} ...>`).
|
||||||
|
// We use it for both `Background.Container` (sidebar / nav
|
||||||
|
// panels) and `Surface.Container` (room view) so the two big
|
||||||
|
// areas read as one uniform surface, divided only by a
|
||||||
|
// 1-pixel line — matches the canon.
|
||||||
|
// bg #181a20 — raised one notch. In the canon this paints chat bubbles,
|
||||||
|
// file cards, the composer container. Mapped to
|
||||||
|
// `SurfaceVariant.Container`.
|
||||||
|
// surface #21232b — raised two notches. Used for chips, reactions, active /
|
||||||
|
// hover rows. Mapped to `*.ContainerActive` family.
|
||||||
|
|
||||||
const darkThemeData = {
|
const darkThemeData = {
|
||||||
Background: {
|
Background: {
|
||||||
Container: navDark,
|
Container: '#0d0e11',
|
||||||
ContainerHover: '#262626',
|
ContainerHover: '#181a20',
|
||||||
ContainerActive: '#333333',
|
ContainerActive: '#21232b',
|
||||||
ContainerLine: '#404040',
|
ContainerLine: '#1f2228',
|
||||||
OnContainer: '#F2F2F2',
|
OnContainer: '#e6e6e9',
|
||||||
},
|
},
|
||||||
|
|
||||||
Surface: {
|
Surface: {
|
||||||
Container: contentDark,
|
Container: '#0d0e11',
|
||||||
ContainerHover: '#333333',
|
ContainerHover: '#181a20',
|
||||||
ContainerActive: '#404040',
|
ContainerActive: '#21232b',
|
||||||
ContainerLine: '#4D4D4D',
|
ContainerLine: '#1f2228',
|
||||||
OnContainer: '#F2F2F2',
|
OnContainer: '#e6e6e9',
|
||||||
},
|
},
|
||||||
|
|
||||||
SurfaceVariant: {
|
SurfaceVariant: {
|
||||||
Container: '#333333',
|
Container: '#181a20',
|
||||||
ContainerHover: '#404040',
|
ContainerHover: '#21232b',
|
||||||
ContainerActive: '#4D4D4D',
|
ContainerActive: '#2a2d36',
|
||||||
ContainerLine: '#595959',
|
ContainerLine: '#2f3340',
|
||||||
OnContainer: '#F2F2F2',
|
OnContainer: '#e6e6e9',
|
||||||
},
|
},
|
||||||
|
|
||||||
Primary: {
|
Primary: {
|
||||||
Main: '#BDB6EC',
|
Main: '#9580ff',
|
||||||
MainHover: '#B2AAE9',
|
MainHover: '#a59cff',
|
||||||
MainActive: '#ADA3E8',
|
MainActive: '#b0a8ff',
|
||||||
MainLine: '#A79DE6',
|
MainLine: '#bcb5ff',
|
||||||
OnMain: '#2C2843',
|
OnMain: '#0c0c0e',
|
||||||
Container: '#413C65',
|
Container: '#3a3260',
|
||||||
ContainerHover: '#494370',
|
ContainerHover: '#443878',
|
||||||
ContainerActive: '#50497B',
|
ContainerActive: '#4d3f87',
|
||||||
ContainerLine: '#575086',
|
ContainerLine: '#564796',
|
||||||
OnContainer: '#E3E1F7',
|
OnContainer: '#e0dcff',
|
||||||
},
|
},
|
||||||
|
|
||||||
Secondary: {
|
Secondary: {
|
||||||
Main: '#FFFFFF',
|
Main: '#e6e6e9',
|
||||||
MainHover: '#E5E5E5',
|
MainHover: '#d6d6d9',
|
||||||
MainActive: '#D9D9D9',
|
MainActive: '#c6c6c9',
|
||||||
MainLine: '#CCCCCC',
|
MainLine: '#b6b6b9',
|
||||||
OnMain: '#1A1A1A',
|
OnMain: '#181a20',
|
||||||
Container: '#404040',
|
Container: '#21232b',
|
||||||
ContainerHover: '#4D4D4D',
|
ContainerHover: '#2a2d36',
|
||||||
ContainerActive: '#595959',
|
ContainerActive: '#34384a',
|
||||||
ContainerLine: '#666666',
|
ContainerLine: '#3a3e54',
|
||||||
OnContainer: '#F2F2F2',
|
OnContainer: '#e6e6e9',
|
||||||
},
|
},
|
||||||
|
|
||||||
Success: {
|
Success: {
|
||||||
Main: '#85E0BA',
|
Main: '#7dd3a8',
|
||||||
MainHover: '#70DBAF',
|
MainHover: '#90dcb5',
|
||||||
MainActive: '#66D9A9',
|
MainActive: '#9ee0bf',
|
||||||
MainLine: '#5CD6A3',
|
MainLine: '#abe5c8',
|
||||||
OnMain: '#0F3D2A',
|
OnMain: '#0e2e1f',
|
||||||
Container: '#175C3F',
|
Container: '#1a4a32',
|
||||||
ContainerHover: '#1A6646',
|
ContainerHover: '#1f5638',
|
||||||
ContainerActive: '#1C704D',
|
ContainerActive: '#23613f',
|
||||||
ContainerLine: '#1F7A54',
|
ContainerLine: '#286c46',
|
||||||
OnContainer: '#CCF2E2',
|
OnContainer: '#caf2d9',
|
||||||
},
|
},
|
||||||
|
|
||||||
Warning: {
|
Warning: {
|
||||||
Main: '#E3BA91',
|
Main: '#d4b88a',
|
||||||
MainHover: '#DFAF7E',
|
MainHover: '#dabf95',
|
||||||
MainActive: '#DDA975',
|
MainActive: '#dfc59e',
|
||||||
MainLine: '#DAA36C',
|
MainLine: '#e3cba8',
|
||||||
OnMain: '#3F2A15',
|
OnMain: '#3a2c14',
|
||||||
Container: '#5E3F20',
|
Container: '#5a4422',
|
||||||
ContainerHover: '#694624',
|
ContainerHover: '#664e27',
|
||||||
ContainerActive: '#734D27',
|
ContainerActive: '#71562b',
|
||||||
ContainerLine: '#7D542B',
|
ContainerLine: '#7c5e30',
|
||||||
OnContainer: '#F3E2D1',
|
OnContainer: '#f3e2c5',
|
||||||
},
|
},
|
||||||
|
|
||||||
Critical: {
|
Critical: {
|
||||||
Main: '#E69D9D',
|
Main: '#c08e7b',
|
||||||
MainHover: '#E28D8D',
|
MainHover: '#c89a88',
|
||||||
MainActive: '#E08585',
|
MainActive: '#cea591',
|
||||||
MainLine: '#DE7D7D',
|
MainLine: '#d4ad9a',
|
||||||
OnMain: '#401C1C',
|
OnMain: '#3a1f17',
|
||||||
Container: '#602929',
|
Container: '#592e22',
|
||||||
ContainerHover: '#6B2E2E',
|
ContainerHover: '#653527',
|
||||||
ContainerActive: '#763333',
|
ContainerActive: '#6f3a2a',
|
||||||
ContainerLine: '#803737',
|
ContainerLine: '#7a402d',
|
||||||
OnContainer: '#F5D6D6',
|
OnContainer: '#f0d4c8',
|
||||||
},
|
},
|
||||||
|
|
||||||
Other: {
|
Other: {
|
||||||
FocusRing: 'rgba(255, 255, 255, 0.5)',
|
FocusRing: 'rgba(149, 128, 255, 0.5)',
|
||||||
Shadow: 'rgba(0, 0, 0, 1)',
|
Shadow: 'rgba(0, 0, 0, 1)',
|
||||||
Overlay: 'rgba(0, 0, 0, 0.8)',
|
Overlay: 'rgba(0, 0, 0, 0.85)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--oq6d070);
|
background-color: var(--vojo-safe-area-bg, #0d0e11);
|
||||||
font-family: var(--font-secondary);
|
font-family: var(--font-secondary);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { enableMapSet } from 'immer';
|
import { enableMapSet } from 'immer';
|
||||||
import '@fontsource/inter/variable.css';
|
import '@fontsource/inter/variable.css';
|
||||||
|
import '@fontsource-variable/jetbrains-mono/index.css';
|
||||||
import 'folds/dist/style.css';
|
import 'folds/dist/style.css';
|
||||||
import { configClass, varsClass } from 'folds';
|
import { configClass, varsClass } from 'folds';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
|
import './app/styles/global.css';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
import { trimTrailingSlash } from './app/utils/common';
|
import { trimTrailingSlash } from './app/utils/common';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,12 @@ import buildConfig from './build.config';
|
||||||
function resolveAppVersion() {
|
function resolveAppVersion() {
|
||||||
if (process.env.VITE_APP_VERSION) return process.env.VITE_APP_VERSION;
|
if (process.env.VITE_APP_VERSION) return process.env.VITE_APP_VERSION;
|
||||||
try {
|
try {
|
||||||
const raw = execSync('git describe --tags --always --dirty', {
|
// --match 'v*' filters out non-semver tags (e.g. `redesign-p0-done`,
|
||||||
|
// `pre-redesign`) so they never shadow the version-derivation chain.
|
||||||
|
// Without it, `git describe` would pick the nearest tag of any shape and
|
||||||
|
// the regex below would fall through to `return raw`, leaking the human
|
||||||
|
// tag name into __APP_VERSION__ on the Welcome screen.
|
||||||
|
const raw = execSync("git describe --tags --match 'v*' --always --dirty", {
|
||||||
stdio: ['ignore', 'pipe', 'ignore'],
|
stdio: ['ignore', 'pipe', 'ignore'],
|
||||||
})
|
})
|
||||||
.toString()
|
.toString()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue