new function settings
This commit is contained in:
@ -12,19 +12,52 @@
|
||||
url('../../assets/fonts/benzin-bold.svg#benzin-bold') format('svg'); /* Chrome < 4, Legacy iOS */
|
||||
}
|
||||
|
||||
:root {
|
||||
--ui-scale: 1;
|
||||
/* SETTINGS NO-BLUR */
|
||||
.glass {
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
#root {
|
||||
transform: scale(var(--ui-scale));
|
||||
transform-origin: top left;
|
||||
|
||||
/* компенсация, чтобы после scale не появлялись пустые области/скроллы */
|
||||
width: calc(100% / var(--ui-scale));
|
||||
height: calc(100% / var(--ui-scale));
|
||||
.glass-ui {
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: relative;
|
||||
color: white;
|
||||
|
||||
@ -108,6 +108,46 @@ const App = () => {
|
||||
};
|
||||
|
||||
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
|
||||
const handleRegister = () => {
|
||||
window.open('https://account.ely.by/register', '_blank');
|
||||
@ -127,104 +167,129 @@ const AppLayout = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch') || location.pathname === '/login' || location.pathname === '/registration'
|
||||
? 'center'
|
||||
: 'flex-start',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
||||
{/* ФОН — НЕ масштабируется */}
|
||||
<Box sx={{ position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none', }}>
|
||||
<MinecraftBackground />
|
||||
<TopBar onRegister={handleRegister} username={username || ''} />
|
||||
<PageHeader />
|
||||
<Notifier />
|
||||
</Box>
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={<Login onLoginSuccess={setUsername} />}
|
||||
/>
|
||||
<Route path="/registration" element={<Registration />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<VersionsExplorer />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/launch/:versionId"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<LaunchPage />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Profile />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/daily"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<DailyReward />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dailyquests"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<DailyQuests />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Settings />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/shop"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Shop />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/marketplace"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Marketplace />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/news"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<News />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
{/* UI — масштабируется */}
|
||||
<Box
|
||||
id="app-scroll"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
id="app-ui"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent:
|
||||
path === '/profile' ||
|
||||
path.startsWith('/launch') ||
|
||||
path === '/login' ||
|
||||
path === '/registration'
|
||||
? 'center'
|
||||
: 'flex-start',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<TopBar onRegister={handleRegister} username={username || ''} />
|
||||
<PageHeader />
|
||||
<Notifier />
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={<Login onLoginSuccess={setUsername} />}
|
||||
/>
|
||||
<Route path="/registration" element={<Registration />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<VersionsExplorer />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/launch/:versionId"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<LaunchPage />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Profile />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/daily"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<DailyReward />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dailyquests"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<DailyQuests />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Settings />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/shop"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Shop />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/marketplace"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Marketplace />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/news"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<News />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -39,13 +39,12 @@ export const FullScreenLoader = ({
|
||||
{fullScreen && (
|
||||
<Fade in timeout={220} appear>
|
||||
<Box
|
||||
className="glass-ui"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
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)',
|
||||
backdropFilter: 'blur(14px)',
|
||||
WebkitBackdropFilter: 'blur(14px)',
|
||||
}}
|
||||
/>
|
||||
</Fade>
|
||||
@ -61,7 +60,9 @@ export const FullScreenLoader = ({
|
||||
alignItems: 'center',
|
||||
gap: 3,
|
||||
// небольшой "подъём" при появлении
|
||||
animation: 'popIn 260ms ease-out both',
|
||||
animation: document.body.classList.contains('reduce-motion')
|
||||
? 'none'
|
||||
: 'popIn 260ms ease-out both',
|
||||
'@keyframes popIn': {
|
||||
from: { opacity: 0, transform: 'translateY(8px) scale(0.98)' },
|
||||
to: { opacity: 1, transform: 'translateY(0) scale(1)' },
|
||||
@ -76,7 +77,9 @@ export const FullScreenLoader = ({
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
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%)',
|
||||
mask: 'radial-gradient(circle, transparent 55%, black 56%)',
|
||||
'@keyframes spin': {
|
||||
|
||||
@ -266,8 +266,18 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
return () => window.removeEventListener('skin-updated', handler as EventListener);
|
||||
}, [loadSkin]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
requestAnimationFrame(updateGradientVars);
|
||||
};
|
||||
|
||||
window.addEventListener('settings-updated', handler as EventListener);
|
||||
return () => window.removeEventListener('settings-updated', handler as EventListener);
|
||||
}, [updateGradientVars]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={isAuthPage ? undefined : 'glass-ui'}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
position: 'fixed',
|
||||
@ -287,7 +297,6 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
// стиль как в Registration
|
||||
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%)',
|
||||
backdropFilter: isAuthPage ? 'none' : 'blur(10px)',
|
||||
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',
|
||||
'&:hover': {
|
||||
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:
|
||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||||
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 2px 8px rgba(0, 0, 0, 0.3)',
|
||||
|
||||
@ -153,11 +153,11 @@ export const News = () => {
|
||||
sx={{
|
||||
px: '7vw',
|
||||
pb: '4vh',
|
||||
maxHeight: '100vh',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2vh',
|
||||
width: '85%',
|
||||
}}
|
||||
>
|
||||
{/* Админский редактор */}
|
||||
@ -353,7 +353,6 @@ export const News = () => {
|
||||
border: '1px solid rgba(255, 255, 255, 0)',
|
||||
boxShadow: '0 18px 45px rgba(0, 0, 0, 0.7)',
|
||||
backdropFilter: 'blur(14px)',
|
||||
width: '80vw',
|
||||
// transition:
|
||||
// 'transform 0.25s ease, box-shadow 0.25s.ease, border-color 0.25s ease',
|
||||
'&:hover': {
|
||||
|
||||
@ -54,7 +54,7 @@ const defaultSettings: SettingsState = {
|
||||
walkingSpeed: 0.5,
|
||||
|
||||
notifications: true,
|
||||
notificationPosition: 'top-right',
|
||||
notificationPosition: 'bottom-center',
|
||||
};
|
||||
|
||||
function safeParseSettings(raw: string | null): SettingsState | null {
|
||||
@ -229,13 +229,10 @@ const Settings = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
const scale = settings.uiScale / 100;
|
||||
document.documentElement.style.setProperty('--ui-scale', String(scale));
|
||||
// motion / blur классы — глобально на body
|
||||
document.body.classList.toggle('reduce-motion', settings.reduceMotion);
|
||||
document.body.classList.toggle('no-blur', !settings.blurEffects);
|
||||
}, [settings.uiScale, settings.reduceMotion, settings.blurEffects]);
|
||||
}, [settings.reduceMotion, settings.blurEffects]);
|
||||
|
||||
const SectionTitle = ({ children }: { children: string }) => (
|
||||
<Typography
|
||||
@ -255,6 +252,7 @@ const Settings = () => {
|
||||
|
||||
const Glass = ({ children }: { children: React.ReactNode }) => (
|
||||
<Paper
|
||||
className="glass glass--soft"
|
||||
elevation={0}
|
||||
sx={{
|
||||
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)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
|
||||
backdropFilter: 'blur(14px)',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
@ -287,6 +284,10 @@ const Settings = () => {
|
||||
},
|
||||
} as const;
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.toggle('no-blur', !settings.blurEffects);
|
||||
}, [settings.blurEffects]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@ -289,6 +289,7 @@ export const VersionsExplorer = () => {
|
||||
flexDirection: 'column',
|
||||
gap: '2vh',
|
||||
height: '100%',
|
||||
width: '85%',
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
|
||||
@ -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' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user