fix and update remote design

This commit is contained in:
jeffvli 2025-06-24 14:36:14 -07:00
parent ad533a1d9c
commit 6689e84f67
24 changed files with 326 additions and 453 deletions

View file

@ -25,6 +25,7 @@
"build:remote": "vite build --config remote.vite.config.ts",
"build:web": "vite build --config web.vite.config.ts",
"dev": "electron-vite dev",
"dev:remote": "vite dev --config remote.vite.config.ts",
"dev:watch": "electron-vite dev --watch",
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
"postinstall": "electron-builder install-app-deps",

View file

@ -25,6 +25,12 @@ export default defineConfig({
},
sourcemap: true,
},
css: {
modules: {
generateScopedName: 'fs-[name]-[local]',
localsConvention: 'camelCase',
},
},
plugins: [
react(),
ViteEjsPlugin({

View file

@ -1,10 +1,15 @@
import { MantineProvider } from '@mantine/core';
import { useEffect } from 'react';
import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';
import './styles/global.css';
import '/@/shared/styles/global.css';
import { useEffect } from 'react';
import { Shell } from '/@/remote/components/shell';
import { useIsDark, useReconnect } from '/@/remote/store';
import { useAppTheme } from '/@/renderer/themes/use-app-theme';
import { AppTheme } from '/@/shared/themes/app-theme-types';
export const App = () => {
const isDark = useIsDark();
@ -14,58 +19,12 @@ export const App = () => {
reconnect();
}, [reconnect]);
const { mode, theme } = useAppTheme(isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT);
return (
<MantineProvider
defaultColorScheme={isDark ? 'dark' : 'light'}
theme={{
components: {
AppShell: {
styles: {
body: {
height: '100vh',
overflow: 'scroll',
},
},
},
Modal: {
styles: {
body: {
background: 'var(--theme-modal-bg)',
height: '100vh',
},
close: { marginRight: '0.5rem' },
content: { borderRadius: '5px' },
header: {
background: 'var(--theme-modal-header-bg)',
paddingBottom: '1rem',
},
title: { fontSize: 'medium', fontWeight: 500 },
},
},
},
defaultRadius: 'xs',
focusRing: 'auto',
fontFamily: 'var(--theme-content-font-family)',
fontSizes: {
lg: '1.1rem',
md: '1rem',
sm: '0.9rem',
xl: '1.5rem',
xs: '0.8rem',
},
headings: {
fontFamily: 'var(--theme-content-font-family)',
fontWeight: '700',
},
other: {},
spacing: {
lg: '2rem',
md: '1rem',
sm: '0.5rem',
xl: '4rem',
xs: '0rem',
},
}}
defaultColorScheme={mode}
theme={theme}
>
<Shell />
</MantineProvider>

View file

@ -1,23 +1,21 @@
import { CiImageOff, CiImageOn } from 'react-icons/ci';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { useShowImage, useToggleShowImage } from '/@/remote/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
export const ImageButton = () => {
const showImage = useShowImage();
const toggleImage = useToggleShowImage();
return (
<RemoteButton
mr={5}
<ActionIcon
onClick={() => toggleImage()}
size="xl"
tooltip={{
label: showImage ? 'Hide Image' : 'Show Image',
}}
variant="default"
>
{showImage ? <CiImageOff size={30} /> : <CiImageOn size={30} />}
</RemoteButton>
</ActionIcon>
);
};

View file

@ -1,24 +1,24 @@
import { RiRestartLine } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { useConnected, useReconnect } from '/@/remote/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
export const ReconnectButton = () => {
const connected = useConnected();
const reconnect = useReconnect();
return (
<RemoteButton
isActive={!connected}
mr={5}
<ActionIcon
onClick={() => reconnect()}
size="xl"
tooltip={{
label: connected ? 'Reconnect' : 'Not connected. Reconnect.',
}}
variant="default"
>
<RiRestartLine size={30} />
</RemoteButton>
<RiRestartLine
color={connected ? 'var(--theme-colors-primary)' : 'var(--theme-colors-foreground)'}
size={30}
/>
</ActionIcon>
);
};

View file

@ -1,24 +0,0 @@
.button {
svg {
display: flex;
fill: var(--theme-colors-foreground);
}
&:hover {
svg {
fill: var(--theme-colors-foreground);
}
}
}
.button.active {
svg {
fill: var(--primary-color);
}
&:hover {
svg {
fill: var(--primary-color) !important;
}
}
}

View file

@ -1,29 +0,0 @@
import clsx from 'clsx';
import { forwardRef, ReactNode, Ref } from 'react';
import styles from './remote-button.module.css';
import { Button, ButtonProps } from '/@/shared/components/button/button';
interface RemoteButtonProps extends ButtonProps {
children: ReactNode;
isActive?: boolean;
ref: Ref<HTMLButtonElement>;
}
export const RemoteButton = forwardRef<HTMLButtonElement, RemoteButtonProps>(
({ children, isActive, tooltip, ...props }, ref) => {
return (
<Button
className={clsx(styles.button, {
[styles.active]: isActive,
})}
tooltip={tooltip}
{...props}
ref={ref}
>
{children}
</Button>
);
},
);

View file

@ -1,30 +1,34 @@
import { useEffect } from 'react';
import { RiMoonLine, RiSunLine } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { useIsDark, useToggleDark } from '/@/remote/store';
import { AppTheme } from '/@/shared/themes/app-theme-types';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Icon } from '/@/shared/components/icon/icon';
export const ThemeButton = () => {
const isDark = useIsDark();
const toggleDark = useToggleDark();
useEffect(() => {
const targetTheme: AppTheme = isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT;
document.body.setAttribute('data-theme', targetTheme);
}, [isDark]);
const handleToggleTheme = () => {
toggleDark();
};
return (
<RemoteButton
mr={5}
onClick={() => toggleDark()}
size="xl"
<ActionIcon
onClick={handleToggleTheme}
tooltip={{
label: 'Toggle Theme',
}}
variant="default"
>
{isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />}
</RemoteButton>
{isDark ? (
<Icon
icon="themeLight"
size={30}
/>
) : (
<Icon
icon="themeDark"
size={30}
/>
)}
</ActionIcon>
);
};

View file

@ -0,0 +1,7 @@
.container {
width: 100%;
height: 40vh;
aspect-ratio: 1/1;
object-fit: var(--theme-image-fit);
border-radius: var(--theme-radius-md);
}

View file

@ -0,0 +1,18 @@
import styles from './player-image.module.css';
import { useSend } from '/@/remote/store';
interface PlayerImageProps {
src?: null | string;
}
export const PlayerImage = ({ src }: PlayerImageProps) => {
const send = useSend();
return (
<img
className={styles.container}
onError={() => send({ event: 'proxy' })}
src={src?.replaceAll(/&(size|width|height=\d+)/g, '')}
/>
);
};

View file

@ -1,24 +1,16 @@
import { Image, Title } from '@mantine/core';
import formatDuration from 'format-duration';
import debounce from 'lodash/debounce';
import { useCallback } from 'react';
import {
RiHeartLine,
RiPauseFill,
RiPlayFill,
RiRepeat2Line,
RiRepeatOneLine,
RiShuffleFill,
RiSkipBackFill,
RiSkipForwardFill,
RiVolumeUpFill,
} from 'react-icons/ri';
import { RiPauseFill, RiPlayFill, RiVolumeUpFill } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
import { PlayerImage } from '/@/remote/components/player-image';
import { WrappedSlider } from '/@/remote/components/wrapped-slider';
import { useInfo, useSend, useShowImage } from '/@/remote/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group';
import { Rating } from '/@/shared/components/rating/rating';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
@ -40,40 +32,115 @@ export const RemoteContainer = () => {
const debouncedSetRating = debounce(setRating, 400);
return (
<>
{id && (
<>
<Title order={1}>{song.name}</Title>
<Group align="flex-end">
<Title order={2}>Album: {song.album}</Title>
<Title order={2}>Artist: {song.artistName}</Title>
</Group>
<Group justify="space-between">
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
{song.releaseDate && (
<Title order={3}>
Released: {new Date(song.releaseDate).toLocaleDateString()}
</Title>
<Stack
gap="md"
h="100dvh"
w="100%"
>
{showImage && (
<Flex
align="center"
justify="center"
w="100%"
>
<PlayerImage src={song?.imageUrl} />
</Flex>
)}
<Title order={3}>Plays: {song.playCount}</Title>
{id && (
<Stack gap="xs">
<Text
fw={700}
size="xl"
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.name}
</Text>
<Text
isMuted
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.album}
</Text>
<Text
isMuted
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.artistName}
</Text>
<Group justify="space-between">
{song.releaseDate && (
<Text isMuted>{new Date(song.releaseDate).toLocaleDateString()}</Text>
)}
<Text isMuted>Plays: {song.playCount}</Text>
</Group>
</>
</Stack>
)}
<Group
gap={0}
grow
>
<RemoteButton
<ActionIcon
disabled={!id}
icon="favorite"
iconProps={{
fill: song?.userFavorite ? 'primary' : 'default',
}}
onClick={() => {
if (!id) return;
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
tooltip={{
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
}}
variant="transparent"
/>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}>
<Tooltip
label="Double click to clear"
openDelay={1000}
>
<Rating
onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)}
style={{ margin: 'auto' }}
value={song.userRating ?? 0}
/>
</Tooltip>
</div>
)}
</Group>
<Group
gap="xs"
grow
>
<ActionIcon
disabled={!id}
icon="mediaPrevious"
iconProps={{
fill: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'previous' })}
tooltip={{
label: 'Previous track',
}}
variant="default"
>
<RiSkipBackFill size={25} />
</RemoteButton>
<RemoteButton
/>
<ActionIcon
disabled={!id}
onClick={() => {
if (status === PlayerStatus.PLAYING) {
@ -92,34 +159,50 @@ export const RemoteContainer = () => {
) : (
<RiPlayFill size={25} />
)}
</RemoteButton>
<RemoteButton
</ActionIcon>
<ActionIcon
disabled={!id}
icon="mediaNext"
iconProps={{
fill: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'next' })}
tooltip={{
label: 'Next track',
}}
variant="default"
>
<RiSkipForwardFill size={25} />
</RemoteButton>
/>
</Group>
<Group
gap={0}
gap="xs"
grow
>
<RemoteButton
isActive={shuffle || false}
<ActionIcon
icon="mediaShuffle"
iconProps={{
fill: shuffle ? 'primary' : 'default',
size: 'lg',
}}
onClick={() => send({ event: 'shuffle' })}
tooltip={{
label: shuffle ? 'Shuffle tracks' : 'Shuffle disabled',
}}
variant="default"
>
<RiShuffleFill size={25} />
</RemoteButton>
<RemoteButton
isActive={repeat !== undefined && repeat !== PlayerRepeat.NONE}
/>
<ActionIcon
icon={
repeat === undefined || repeat === PlayerRepeat.ONE
? 'mediaRepeatOne'
: 'mediaRepeat'
}
iconProps={{
fill:
repeat !== undefined && repeat !== PlayerRepeat.NONE
? 'primary'
: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'repeat' })}
tooltip={{
label: `Repeat ${
@ -131,46 +214,11 @@ export const RemoteContainer = () => {
}`,
}}
variant="default"
>
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
<RiRepeatOneLine size={25} />
) : (
<RiRepeat2Line size={25} />
)}
</RemoteButton>
<RemoteButton
disabled={!id}
isActive={song?.userFavorite}
onClick={() => {
if (!id) return;
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
tooltip={{
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
}}
variant="default"
>
<RiHeartLine size={25} />
</RemoteButton>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}>
<Tooltip
label="Double click to clear"
openDelay={1000}
>
<Rating
onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)}
style={{ margin: 'auto' }}
value={song.userRating ?? 0}
/>
</Tooltip>
</div>
)}
</Group>
<Stack gap="lg">
{id && position !== undefined && (
<WrapperSlider
<WrappedSlider
label={(value) => formatDuration(value * 1e3)}
leftLabel={formatDuration(position * 1e3)}
max={song.duration / 1e3}
@ -179,7 +227,7 @@ export const RemoteContainer = () => {
value={position}
/>
)}
<WrapperSlider
<WrappedSlider
leftLabel={<RiVolumeUpFill size={20} />}
max={100}
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
@ -193,12 +241,7 @@ export const RemoteContainer = () => {
}
value={volume ?? 0}
/>
{showImage && (
<Image
onError={() => send({ event: 'proxy' })}
src={song?.imageUrl?.replaceAll(/&(size|width|height=\d+)/g, '')}
/>
)}
</>
</Stack>
</Stack>
);
};

View file

@ -1,54 +1,71 @@
import { AppShell, Container, Flex, Grid, Image, Skeleton, Title } from '@mantine/core';
import { AppShell, Flex, Grid, Image } from '@mantine/core';
import { ImageButton } from '/@/remote/components/buttons/image-button';
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
import { ThemeButton } from '/@/remote/components/buttons/theme-button';
import { RemoteContainer } from '/@/remote/components/remote-container';
import { useConnected } from '/@/remote/store';
import { Center } from '/@/shared/components/center/center';
import { Group } from '/@/shared/components/group/group';
import { Spinner } from '/@/shared/components/spinner/spinner';
export const Shell = () => {
const connected = useConnected();
return (
<AppShell padding="md">
<AppShell.Header>
<Grid>
<Grid.Col span="auto">
<div>
<AppShell
h="100vh"
padding="md"
w="100vw"
>
<AppShell.Header style={{ background: 'var(--theme-colors-surface)' }}>
<Grid
px="md"
py="sm"
>
<Grid.Col span={4}>
<Flex
align="center"
direction="row"
h="100%"
justify="flex-start"
style={{
justifySelf: 'flex-start',
}}
>
<Image
fit="contain"
height={60}
height={32}
src="/favicon.ico"
width={60}
width={32}
/>
</div>
</Flex>
</Grid.Col>
<Grid.Col hiddenFrom="md">
<Title ta="center">Feishin Remote</Title>
</Grid.Col>
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
<Grid.Col span={8}>
<Group
gap="sm"
justify="flex-end"
wrap="nowrap"
>
<ReconnectButton />
<ImageButton />
<ThemeButton />
</Flex>
</Group>
</Grid.Col>
</Grid>
</AppShell.Header>
<Container>
<AppShell.Main pt="60px">
{connected ? (
<RemoteContainer />
) : (
<Skeleton
height={300}
width="100%"
/>
<Center
h="100vh"
w="100vw"
>
<Spinner />
</Center>
)}
</Container>
</AppShell.Main>
</AppShell>
);
};

View file

@ -1,21 +0,0 @@
.container {
display: flex;
width: 95%;
height: 20px;
margin: 10px 0;
}
.wrapper {
display: flex;
flex: 6;
align-items: center;
height: 100%;
}
.value-wrapper {
display: flex;
flex: 1;
align-self: flex-end;
justify-content: center;
max-width: 50px;
}

View file

@ -1,7 +1,8 @@
import { rem, Slider, SliderProps } from '@mantine/core';
import { ReactNode, useState } from 'react';
import styles from './wrapped-slider.module.css';
import { Group } from '/@/shared/components/group/group';
import { Text } from '/@/shared/components/text/text';
const PlayerbarSlider = ({ ...props }: SliderProps) => {
return (
@ -37,6 +38,7 @@ const PlayerbarSlider = ({ ...props }: SliderProps) => {
'&::before': {
right: 'calc(0.1rem * -1)',
},
height: '1rem',
},
}}
{...props}
@ -54,14 +56,16 @@ export interface WrappedProps extends Omit<SliderProps, 'onChangeEnd'> {
value: number;
}
export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: WrappedProps) => {
export const WrappedSlider = ({ leftLabel, rightLabel, value, ...props }: WrappedProps) => {
const [isSeeking, setIsSeeking] = useState(false);
const [seek, setSeek] = useState(0);
return (
<div className={styles.container}>
{leftLabel && <div className={styles.valueWrapper}>{leftLabel}</div>}
<div className={styles.wrapper}>
<Group
align="center"
wrap="nowrap"
>
{leftLabel && <Text size="sm">{leftLabel}</Text>}
<PlayerbarSlider
{...props}
min={0}
@ -77,8 +81,7 @@ export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
value={!isSeeking ? (value ?? 0) : seek}
w="100%"
/>
</div>
{rightLabel && <div className={styles.valueWrapper}>{rightLabel}</div>}
</div>
{rightLabel && <Text size="sm">{rightLabel}</Text>}
</Group>
);
};

View file

@ -5,6 +5,9 @@
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Feishin Remote</title>
<link rel="manifest" href="manifest.json">
<script>

View file

@ -1,4 +1,3 @@
import { Notifications } from '@mantine/notifications';
import { createRoot } from 'react-dom/client';
import { App } from '/@/remote/app';
@ -6,12 +5,4 @@ import { App } from '/@/remote/app';
const container = document.getElementById('root')! as HTMLElement;
const root = createRoot(container);
root.render(
<>
<Notifications
containerWidth="300px"
position="bottom-center"
/>
<App />
</>,
);
root.render(<App />);

View file

@ -1,112 +0,0 @@
@import url('../../renderer/styles/ag-grid.css');
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body,
html {
position: absolute;
display: block;
width: 100%;
height: 100%;
overflow: hidden;
font-family: var(--theme-content-font-family);
font-size: var(--theme-root-font-size);
color: var(--theme-content-text-color);
user-select: none;
background: var(--theme-content-bg);
}
@media only screen and (width < 640px) {
body,
html {
overflow-x: auto;
}
}
#app {
height: inherit;
}
*,
*::before,
*::after {
box-sizing: border-box;
text-rendering: optimizelegibility;
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
text-size-adjust: none;
outline: none;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
/* ::-webkit-scrollbar-corner {
background: var(--theme-scrollbar-track-background);
}
::-webkit-scrollbar-track {
background: var(--theme-scrollbar-track-background);
}
::-webkit-scrollbar-thumb {
background: var(--theme-scrollbar-handle-background);
}
::-webkit-scrollbar-thumb:hover {
background: var(--theme-scrollbar-handle-hover-background);
} */
a {
text-decoration: none;
}
button {
-webkit-app-region: no-drag;
}
.overlay-scrollbar {
overflow: auto !important;
}
.hide-scrollbar {
scrollbar-color: transparent transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 1px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

View file

@ -9,13 +9,12 @@ import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';
import '@mantine/dates/styles.css';
import './styles/global.css';
import '/@/shared/styles/global.css';
import '@ag-grid-community/styles/ag-grid.css';
import 'overlayscrollbars/overlayscrollbars.css';
import './styles/overlayscrollbars.css';
import '/styles/overlayscrollbars.css';
import i18n from '/@/i18n/i18n';
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';

View file

@ -12,7 +12,7 @@ export const THEME_DATA = [
{ label: 'Default Light', type: 'light', value: AppTheme.DEFAULT_LIGHT },
];
export const useAppTheme = () => {
export const useAppTheme = (overrideTheme?: AppTheme) => {
const accent = useSettingsStore((store) => store.general.accent);
const nativeImageAspect = useSettingsStore((store) => store.general.nativeAspectRatio);
const { builtIn, custom, system, type } = useSettingsStore((state) => state.font);
@ -28,6 +28,10 @@ export const useAppTheme = () => {
};
const getSelectedTheme = () => {
if (overrideTheme) {
return overrideTheme;
}
if (followSystemTheme) {
return isDarkTheme ? themeDark : themeLight;
}

View file

@ -63,6 +63,7 @@ import {
LuLogOut,
LuMenu,
LuMinus,
LuMoon,
LuMusic,
LuMusic2,
LuPanelRightClose,
@ -88,6 +89,7 @@ import {
LuStar,
LuStepBack,
LuStepForward,
LuSun,
LuTable,
LuTriangleAlert,
LuUser,
@ -204,6 +206,8 @@ export const AppIcon = {
squareCheck: LuSquareCheck,
star: LuStar,
success: LuCircleCheck,
themeDark: LuMoon,
themeLight: LuSun,
track: LuMusic2,
unfavorite: LuHeartCrack,
user: LuUser,

View file

@ -122,56 +122,59 @@ button {
@font-face {
font-family: Archivo;
font-weight: 100 1000;
src: url('../fonts/Archivo-VariableFont_wdth,wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Archivo-VariableFont_wdth,wght.ttf')
format('truetype-variations');
}
@font-face {
font-family: Raleway;
font-weight: 100 1000;
src: url('../fonts/Raleway-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Raleway-VariableFont_wght.ttf') format('truetype-variations');
}
@font-face {
font-family: Fredoka;
font-weight: 100 1000;
src: url('../fonts/Fredoka-VariableFont_wdth,wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Fredoka-VariableFont_wdth,wght.ttf')
format('truetype-variations');
}
@font-face {
font-family: 'League Spartan';
font-weight: 100 1000;
src: url('../fonts/LeagueSpartan-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/LeagueSpartan-VariableFont_wght.ttf')
format('truetype-variations');
}
@font-face {
font-family: Lexend;
font-weight: 100 1000;
src: url('../fonts/Lexend-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Lexend-VariableFont_wght.ttf') format('truetype-variations');
}
@font-face {
font-family: Inter;
font-weight: 100 1000;
src: url('../fonts/Inter-VariableFont_slnt,wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Inter-VariableFont_slnt,wght.ttf') format('truetype-variations');
}
@font-face {
font-family: Sora;
font-weight: 100 1000;
src: url('../fonts/Sora-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Sora-VariableFont_wght.ttf') format('truetype-variations');
}
@font-face {
font-family: 'Work Sans';
font-weight: 100 1000;
src: url('../fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
}
@font-face {
font-family: Poppins;
font-style: normal;
font-weight: 400;
src: url('../fonts/Poppins-Regular.ttf') format('truetype');
src: url('../../renderer/fonts/Poppins-Regular.ttf') format('truetype');
font-display: swap;
}
@ -179,7 +182,7 @@ button {
font-family: Poppins;
font-style: normal;
font-weight: 600;
src: url('../fonts/Poppins-SemiBold.ttf') format('truetype');
src: url('../../renderer/fonts/Poppins-SemiBold.ttf') format('truetype');
font-display: swap;
}
@ -187,7 +190,7 @@ button {
font-family: Poppins;
font-style: normal;
font-weight: 700;
src: url('../fonts/Poppins-Bold.ttf') format('truetype');
src: url('../../renderer/fonts/Poppins-Bold.ttf') format('truetype');
font-display: swap;
}
@ -195,7 +198,7 @@ button {
font-family: Poppins;
font-style: normal;
font-weight: 800;
src: url('../fonts/Poppins-ExtraBold.ttf') format('truetype');
src: url('../../renderer/fonts/Poppins-ExtraBold.ttf') format('truetype');
font-display: swap;
}
@ -203,14 +206,14 @@ button {
font-family: Poppins;
font-style: normal;
font-weight: 900;
src: url('../fonts/Poppins-Black.ttf') format('truetype');
src: url('../../renderer/fonts/Poppins-Black.ttf') format('truetype');
font-display: swap;
}
@font-face {
font-family: Raleway;
font-weight: 100 1000;
src: url('../fonts/Raleway-VariableFont_wght.ttf') format('truetype-variations');
src: url('../../renderer/fonts/Raleway-VariableFont_wght.ttf') format('truetype-variations');
}
@font-face {

View file

@ -21,7 +21,6 @@ export default defineConfig({
},
css: {
modules: {
generateScopedName: '[name]__[local]__[hash:base64:5]',
localsConvention: 'camelCase',
},
},