Compare commits
2 Commits
ff87c9d4a5
...
6adc64dab8
| Author | SHA1 | Date | |
|---|---|---|---|
| 6adc64dab8 | |||
| 11a203cb8f |
@ -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;
|
||||||
|
|||||||
@ -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,104 +167,129 @@ const AppLayout = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
||||||
sx={{
|
{/* ФОН — НЕ масштабируется */}
|
||||||
height: '100vh',
|
<Box sx={{ position: 'fixed', inset: 0, zIndex: 0, pointerEvents: 'none', }}>
|
||||||
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',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MinecraftBackground />
|
<MinecraftBackground />
|
||||||
<TopBar onRegister={handleRegister} username={username || ''} />
|
</Box>
|
||||||
<PageHeader />
|
|
||||||
<Notifier />
|
|
||||||
|
|
||||||
<Routes>
|
{/* UI — масштабируется */}
|
||||||
<Route
|
<Box
|
||||||
path="/login"
|
id="app-scroll"
|
||||||
element={<Login onLoginSuccess={setUsername} />}
|
sx={{
|
||||||
/>
|
position: 'relative',
|
||||||
<Route path="/registration" element={<Registration />} />
|
zIndex: 1,
|
||||||
<Route
|
height: '100%',
|
||||||
path="/"
|
width: '100%',
|
||||||
element={
|
overflowY: 'hidden',
|
||||||
<AuthCheck>
|
overflowX: 'hidden',
|
||||||
<VersionsExplorer />
|
}}
|
||||||
</AuthCheck>
|
>
|
||||||
}
|
<Box
|
||||||
/>
|
id="app-ui"
|
||||||
<Route
|
sx={{
|
||||||
path="/launch/:versionId"
|
position: 'relative',
|
||||||
element={
|
zIndex: 1,
|
||||||
<AuthCheck>
|
height: '100vh',
|
||||||
<LaunchPage />
|
width: '100vw',
|
||||||
</AuthCheck>
|
display: 'flex',
|
||||||
}
|
flexDirection: 'column',
|
||||||
/>
|
alignItems: 'center',
|
||||||
<Route
|
justifyContent:
|
||||||
path="/profile"
|
path === '/profile' ||
|
||||||
element={
|
path.startsWith('/launch') ||
|
||||||
<AuthCheck>
|
path === '/login' ||
|
||||||
<Profile />
|
path === '/registration'
|
||||||
</AuthCheck>
|
? 'center'
|
||||||
}
|
: 'flex-start',
|
||||||
/>
|
overflowX: 'hidden',
|
||||||
<Route
|
}}
|
||||||
path="/daily"
|
>
|
||||||
element={
|
<TopBar onRegister={handleRegister} username={username || ''} />
|
||||||
<AuthCheck>
|
<PageHeader />
|
||||||
<DailyReward />
|
<Notifier />
|
||||||
</AuthCheck>
|
|
||||||
}
|
<Routes>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/login"
|
||||||
path="/dailyquests"
|
element={<Login onLoginSuccess={setUsername} />}
|
||||||
element={
|
/>
|
||||||
<AuthCheck>
|
<Route path="/registration" element={<Registration />} />
|
||||||
<DailyQuests />
|
<Route
|
||||||
</AuthCheck>
|
path="/"
|
||||||
}
|
element={
|
||||||
/>
|
<AuthCheck>
|
||||||
<Route
|
<VersionsExplorer />
|
||||||
path="/settings"
|
</AuthCheck>
|
||||||
element={
|
}
|
||||||
<AuthCheck>
|
/>
|
||||||
<Settings />
|
<Route
|
||||||
</AuthCheck>
|
path="/launch/:versionId"
|
||||||
}
|
element={
|
||||||
/>
|
<AuthCheck>
|
||||||
<Route
|
<LaunchPage />
|
||||||
path="/shop"
|
</AuthCheck>
|
||||||
element={
|
}
|
||||||
<AuthCheck>
|
/>
|
||||||
<Shop />
|
<Route
|
||||||
</AuthCheck>
|
path="/profile"
|
||||||
}
|
element={
|
||||||
/>
|
<AuthCheck>
|
||||||
<Route
|
<Profile />
|
||||||
path="/marketplace"
|
</AuthCheck>
|
||||||
element={
|
}
|
||||||
<AuthCheck>
|
/>
|
||||||
<Marketplace />
|
<Route
|
||||||
</AuthCheck>
|
path="/daily"
|
||||||
}
|
element={
|
||||||
/>
|
<AuthCheck>
|
||||||
<Route
|
<DailyReward />
|
||||||
path="/news"
|
</AuthCheck>
|
||||||
element={
|
}
|
||||||
<AuthCheck>
|
/>
|
||||||
<News />
|
<Route
|
||||||
</AuthCheck>
|
path="/dailyquests"
|
||||||
}
|
element={
|
||||||
/>
|
<AuthCheck>
|
||||||
</Routes>
|
<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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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': {
|
||||||
|
|||||||
@ -251,8 +251,18 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
window.removeEventListener('skin-updated', handler as EventListener);
|
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',
|
||||||
|
|||||||
@ -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': {
|
||||||
|
|||||||
@ -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={{
|
||||||
|
|||||||
@ -289,6 +289,7 @@ export const VersionsExplorer = () => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '2vh',
|
gap: '2vh',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
width: '85%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{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