new function settings

This commit is contained in:
aurinex
2025-12-15 00:37:45 +05:00
parent 645de4248e
commit 11a203cb8f
8 changed files with 232 additions and 153 deletions

View File

@ -12,19 +12,52 @@
url('../../assets/fonts/benzin-bold.svg#benzin-bold') format('svg'); /* Chrome < 4, Legacy iOS */ url('../../assets/fonts/benzin-bold.svg#benzin-bold') format('svg'); /* Chrome < 4, Legacy iOS */
} }
:root { /* SETTINGS NO-BLUR */
--ui-scale: 1; .glass {
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
} }
#root { .glass-ui {
transform: scale(var(--ui-scale)); backdrop-filter: blur(10px);
transform-origin: top left; -webkit-backdrop-filter: blur(10px);
/* компенсация, чтобы после scale не появлялись пустые области/скроллы */
width: calc(100% / var(--ui-scale));
height: calc(100% / var(--ui-scale));
} }
.glass--soft { backdrop-filter: blur(6px); }
.glass--hard { backdrop-filter: blur(20px); }
body.no-blur .glass,
body.no-blur .glass-ui {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* SETTINGS NO-BLUR */
/* SETTINGS REDUCE-MOTION */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
body.reduce-motion *,
body.reduce-motion *::before,
body.reduce-motion *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
/* опционально: убрать ховер-скейлы (если ты их часто используешь) */
body.reduce-motion .no-motion-hover,
body.reduce-motion .no-motion-hover:hover {
transform: none !important;
}
/* SETTINGS REDUCE-MOTION */
body { body {
position: relative; position: relative;
color: white; color: white;

View File

@ -108,6 +108,46 @@ const App = () => {
}; };
const AppLayout = () => { const AppLayout = () => {
useEffect(() => {
const applySettings = () => {
try {
const raw = localStorage.getItem('launcher_settings');
if (!raw) return;
const settings = JSON.parse(raw);
document.body.classList.toggle(
'reduce-motion',
Boolean(settings.reduceMotion),
);
document.body.classList.toggle(
'no-blur',
settings.blurEffects === false,
);
const ui = document.getElementById('app-ui');
if (ui && typeof settings.uiScale === 'number') {
const scale = settings.uiScale / 100;
ui.style.transform = `scale(${scale})`;
ui.style.transformOrigin = 'top left';
ui.style.width = `${100 / scale}%`;
ui.style.height = `${100 / scale}%`;
}
} catch (e) {
console.error('Failed to apply UI settings', e);
}
};
// применяем при загрузке
applySettings();
// применяем после сохранения настроек
window.addEventListener('settings-updated', applySettings);
return () => window.removeEventListener('settings-updated', applySettings);
}, []);
// Просто используйте window.open без useNavigate // Просто используйте window.open без useNavigate
const handleRegister = () => { const handleRegister = () => {
window.open('https://account.ely.by/register', '_blank'); window.open('https://account.ely.by/register', '_blank');
@ -127,21 +167,44 @@ const AppLayout = () => {
}, []); }, []);
return ( return (
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
{/* ФОН — НЕ масштабируется */}
<Box sx={{ position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none', }}>
<MinecraftBackground />
</Box>
{/* UI — масштабируется */}
<Box <Box
id="app-scroll"
sx={{ sx={{
position: 'relative',
zIndex: 1,
height: '100%',
width: '100%',
overflowY: 'hidden',
overflowX: 'hidden',
}}
>
<Box
id="app-ui"
sx={{
position: 'relative',
zIndex: 1,
height: '100vh', height: '100vh',
width: '100vw', width: '100vw',
position: 'relative',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch') || location.pathname === '/login' || location.pathname === '/registration' justifyContent:
path === '/profile' ||
path.startsWith('/launch') ||
path === '/login' ||
path === '/registration'
? 'center' ? 'center'
: 'flex-start', : 'flex-start',
overflowX: 'hidden', overflowX: 'hidden',
}} }}
> >
<MinecraftBackground />
<TopBar onRegister={handleRegister} username={username || ''} /> <TopBar onRegister={handleRegister} username={username || ''} />
<PageHeader /> <PageHeader />
<Notifier /> <Notifier />
@ -226,6 +289,8 @@ const AppLayout = () => {
/> />
</Routes> </Routes>
</Box> </Box>
</Box>
</Box>
); );
}; };

View File

@ -39,13 +39,12 @@ export const FullScreenLoader = ({
{fullScreen && ( {fullScreen && (
<Fade in timeout={220} appear> <Fade in timeout={220} appear>
<Box <Box
className="glass-ui"
sx={{ sx={{
position: 'absolute', position: 'absolute',
inset: 0, inset: 0,
background: background:
'radial-gradient(circle at 15% 20%, rgba(242,113,33,0.15), transparent 60%), radial-gradient(circle at 85% 10%, rgba(233,64,205,0.12), transparent 55%), rgba(5,5,10,0.75)', 'radial-gradient(circle at 15% 20%, rgba(242,113,33,0.15), transparent 60%), radial-gradient(circle at 85% 10%, rgba(233,64,205,0.12), transparent 55%), rgba(5,5,10,0.75)',
backdropFilter: 'blur(14px)',
WebkitBackdropFilter: 'blur(14px)',
}} }}
/> />
</Fade> </Fade>
@ -61,7 +60,9 @@ export const FullScreenLoader = ({
alignItems: 'center', alignItems: 'center',
gap: 3, gap: 3,
// небольшой "подъём" при появлении // небольшой "подъём" при появлении
animation: 'popIn 260ms ease-out both', animation: document.body.classList.contains('reduce-motion')
? 'none'
: 'popIn 260ms ease-out both',
'@keyframes popIn': { '@keyframes popIn': {
from: { opacity: 0, transform: 'translateY(8px) scale(0.98)' }, from: { opacity: 0, transform: 'translateY(8px) scale(0.98)' },
to: { opacity: 1, transform: 'translateY(0) scale(1)' }, to: { opacity: 1, transform: 'translateY(0) scale(1)' },
@ -76,7 +77,9 @@ export const FullScreenLoader = ({
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
background: 'conic-gradient(#F27121, #E940CD, #8A2387, #F27121)', background: 'conic-gradient(#F27121, #E940CD, #8A2387, #F27121)',
animation: 'spin 1s linear infinite', animation: document.body.classList.contains('reduce-motion')
? 'none'
: 'spin 1s linear infinite',
WebkitMask: 'radial-gradient(circle, transparent 55%, black 56%)', WebkitMask: 'radial-gradient(circle, transparent 55%, black 56%)',
mask: 'radial-gradient(circle, transparent 55%, black 56%)', mask: 'radial-gradient(circle, transparent 55%, black 56%)',
'@keyframes spin': { '@keyframes spin': {

View File

@ -266,8 +266,18 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
return () => window.removeEventListener('skin-updated', handler as EventListener); return () => window.removeEventListener('skin-updated', handler as EventListener);
}, [loadSkin]); }, [loadSkin]);
useEffect(() => {
const handler = () => {
requestAnimationFrame(updateGradientVars);
};
window.addEventListener('settings-updated', handler as EventListener);
return () => window.removeEventListener('settings-updated', handler as EventListener);
}, [updateGradientVars]);
return ( return (
<Box <Box
className={isAuthPage ? undefined : 'glass-ui'}
sx={{ sx={{
display: 'flex', display: 'flex',
position: 'fixed', position: 'fixed',
@ -287,7 +297,6 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
// стиль как в Registration // стиль как в Registration
background: isAuthPage ? background: isAuthPage ?
'none' : 'linear-gradient(71deg, rgba(242,113,33,0.18) 0%, rgba(233,64,205,0.14) 70%, rgba(138,35,135,0.16) 100%)', 'none' : 'linear-gradient(71deg, rgba(242,113,33,0.18) 0%, rgba(233,64,205,0.14) 70%, rgba(138,35,135,0.16) 100%)',
backdropFilter: isAuthPage ? 'none' : 'blur(10px)',
boxShadow: isAuthPage ? 'none' : '0 8px 30px rgba(0,0,0,0.35)', boxShadow: isAuthPage ? 'none' : '0 8px 30px rgba(0,0,0,0.35)',
}} }}
> >
@ -316,6 +325,7 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
transition: 'transform 0.3s ease', transition: 'transform 0.3s ease',
'&:hover': { '&:hover': {
transform: 'scale(1.2)', transform: 'scale(1.2)',
...(document.body.classList.contains('reduce-motion') ? { transform: 'none' } : null),
}, },
}} }}
> >
@ -693,6 +703,7 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
background: background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)', 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transform: 'scale(1.01)', transform: 'scale(1.01)',
...(document.body.classList.contains('reduce-motion') ? { transform: 'none' } : null),
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)', boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
}, },
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',

View File

@ -153,11 +153,11 @@ export const News = () => {
sx={{ sx={{
px: '7vw', px: '7vw',
pb: '4vh', pb: '4vh',
maxHeight: '100vh',
overflowY: 'auto', overflowY: 'auto',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '2vh', gap: '2vh',
width: '85%',
}} }}
> >
{/* Админский редактор */} {/* Админский редактор */}
@ -353,7 +353,6 @@ export const News = () => {
border: '1px solid rgba(255, 255, 255, 0)', border: '1px solid rgba(255, 255, 255, 0)',
boxShadow: '0 18px 45px rgba(0, 0, 0, 0.7)', boxShadow: '0 18px 45px rgba(0, 0, 0, 0.7)',
backdropFilter: 'blur(14px)', backdropFilter: 'blur(14px)',
width: '80vw',
// transition: // transition:
// 'transform 0.25s ease, box-shadow 0.25s.ease, border-color 0.25s ease', // 'transform 0.25s ease, box-shadow 0.25s.ease, border-color 0.25s ease',
'&:hover': { '&:hover': {

View File

@ -54,7 +54,7 @@ const defaultSettings: SettingsState = {
walkingSpeed: 0.5, walkingSpeed: 0.5,
notifications: true, notifications: true,
notificationPosition: 'top-right', notificationPosition: 'bottom-center',
}; };
function safeParseSettings(raw: string | null): SettingsState | null { function safeParseSettings(raw: string | null): SettingsState | null {
@ -229,13 +229,10 @@ const Settings = () => {
}; };
useEffect(() => { useEffect(() => {
if (typeof document === 'undefined') return; // motion / blur классы — глобально на body
const scale = settings.uiScale / 100;
document.documentElement.style.setProperty('--ui-scale', String(scale));
document.body.classList.toggle('reduce-motion', settings.reduceMotion); document.body.classList.toggle('reduce-motion', settings.reduceMotion);
document.body.classList.toggle('no-blur', !settings.blurEffects); document.body.classList.toggle('no-blur', !settings.blurEffects);
}, [settings.uiScale, settings.reduceMotion, settings.blurEffects]); }, [settings.reduceMotion, settings.blurEffects]);
const SectionTitle = ({ children }: { children: string }) => ( const SectionTitle = ({ children }: { children: string }) => (
<Typography <Typography
@ -255,6 +252,7 @@ const Settings = () => {
const Glass = ({ children }: { children: React.ReactNode }) => ( const Glass = ({ children }: { children: React.ReactNode }) => (
<Paper <Paper
className="glass glass--soft"
elevation={0} elevation={0}
sx={{ sx={{
borderRadius: '1.2vw', borderRadius: '1.2vw',
@ -263,7 +261,6 @@ const Settings = () => {
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.14), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.12), transparent 55%), rgba(10,10,20,0.86)', 'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.14), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.12), transparent 55%), rgba(10,10,20,0.86)',
border: '1px solid rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.08)',
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)', boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
backdropFilter: 'blur(14px)',
color: 'white', color: 'white',
}} }}
> >
@ -287,6 +284,10 @@ const Settings = () => {
}, },
} as const; } as const;
useEffect(() => {
document.body.classList.toggle('no-blur', !settings.blurEffects);
}, [settings.blurEffects]);
return ( return (
<Box <Box
sx={{ sx={{

View File

@ -289,6 +289,7 @@ export const VersionsExplorer = () => {
flexDirection: 'column', flexDirection: 'column',
gap: '2vh', gap: '2vh',
height: '100%', height: '100%',
width: '85%',
}} }}
> >
{loading ? ( {loading ? (

View File

@ -1,34 +0,0 @@
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
export type LauncherSettings = {
notificationPosition?: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left';
};
export function getLauncherSettings(): LauncherSettings {
try {
return JSON.parse(localStorage.getItem('launcher_settings') || '{}');
} catch {
return {};
}
}
export function getNotificationPosition(): NotificationPosition {
const { notificationPosition } = getLauncherSettings();
switch (notificationPosition) {
case 'top-right':
return { vertical: 'top', horizontal: 'right' };
case 'top-center':
return { vertical: 'top', horizontal: 'center' };
case 'top-left':
return { vertical: 'top', horizontal: 'left' };
case 'bottom-right':
return { vertical: 'bottom', horizontal: 'right' };
case 'bottom-center':
return { vertical: 'bottom', horizontal: 'center' };
case 'bottom-left':
return { vertical: 'bottom', horizontal: 'left' };
default:
return { vertical: 'bottom', horizontal: 'center' };
}
}