diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts
index a6ca87e..4194e06 100644
--- a/.erb/configs/webpack.config.renderer.dev.ts
+++ b/.erb/configs/webpack.config.renderer.dev.ts
@@ -112,6 +112,13 @@ const configuration: webpack.Configuration = {
'file-loader',
],
},
+ {
+ test: /\.(mp3|wav|ogg)$/i,
+ type: 'asset/resource',
+ generator: {
+ filename: 'assets/sounds/[name][ext]',
+ },
+ }
],
},
plugins: [
diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts
index 3cebf30..1536358 100644
--- a/.erb/configs/webpack.config.renderer.prod.ts
+++ b/.erb/configs/webpack.config.renderer.prod.ts
@@ -88,6 +88,13 @@ const configuration: webpack.Configuration = {
'file-loader',
],
},
+ {
+ test: /\.(mp3|wav|ogg)$/i,
+ type: 'asset/resource',
+ generator: {
+ filename: 'assets/sounds/[name][ext]',
+ },
+ }
],
},
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 747ebe8..a006b34 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -133,7 +133,7 @@ const AppLayout = () => {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
- justifyContent: location.pathname === '/profile'
+ justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch')
? 'center'
: 'flex-start',
overflowX: 'hidden',
diff --git a/assets/assets.d.ts b/src/renderer/assets.d.ts
similarity index 74%
rename from assets/assets.d.ts
rename to src/renderer/assets.d.ts
index 251085e..c8b7e3e 100644
--- a/assets/assets.d.ts
+++ b/src/renderer/assets.d.ts
@@ -33,3 +33,16 @@ declare module '*.css' {
const content: Styles;
export default content;
}
+
+declare module '*.mp3' {
+ const src: string;
+ export default src;
+}
+declare module '*.wav' {
+ const src: string;
+ export default src;
+}
+declare module '*.ogg' {
+ const src: string;
+ export default src;
+}
\ No newline at end of file
diff --git a/src/renderer/components/Icons/GradientVisibilityToggleIcon.tsx b/src/renderer/assets/Icons/GradientVisibilityToggleIcon.tsx
similarity index 100%
rename from src/renderer/components/Icons/GradientVisibilityToggleIcon.tsx
rename to src/renderer/assets/Icons/GradientVisibilityToggleIcon.tsx
diff --git a/src/renderer/assets/sounds/buy.mp3 b/src/renderer/assets/sounds/buy.mp3
new file mode 100644
index 0000000..87a2016
Binary files /dev/null and b/src/renderer/assets/sounds/buy.mp3 differ
diff --git a/src/renderer/assets/sounds/sell.mp3 b/src/renderer/assets/sounds/sell.mp3
new file mode 100644
index 0000000..b2b8df4
Binary files /dev/null and b/src/renderer/assets/sounds/sell.mp3 differ
diff --git a/src/renderer/components/CoinsDisplay.tsx b/src/renderer/components/CoinsDisplay.tsx
index 9e1e6b6..1840d6a 100644
--- a/src/renderer/components/CoinsDisplay.tsx
+++ b/src/renderer/components/CoinsDisplay.tsx
@@ -53,28 +53,28 @@ export default function CoinsDisplay({
switch (size) {
case 'small':
return {
- containerPadding: '4px 8px',
- iconSize: '16px',
- fontSize: '12px',
- borderRadius: '12px',
- gap: '6px',
+ containerPadding: '0.4vw 0.8vw',
+ iconSize: '1.4vw',
+ fontSize: '1vw',
+ borderRadius: '2vw',
+ gap: '0.6vw',
};
case 'large':
return {
- containerPadding: '8px 16px',
- iconSize: '28px',
- fontSize: '18px',
- borderRadius: '20px',
- gap: '10px',
+ containerPadding: '0.4vw 1.2vw',
+ iconSize: '2.2vw',
+ fontSize: '1.6vw',
+ borderRadius: '1.8vw',
+ gap: '0.8vw',
};
case 'medium':
default:
return {
- containerPadding: '6px 12px',
- iconSize: '24px',
- fontSize: '16px',
- borderRadius: '16px',
- gap: '8px',
+ containerPadding: '0.4vw 1vw',
+ iconSize: '2vw',
+ fontSize: '1.4vw',
+ borderRadius: '1.6vw',
+ gap: '0.6vw',
};
}
};
diff --git a/src/renderer/components/Login/AuthForm.tsx b/src/renderer/components/Login/AuthForm.tsx
index 731a989..8d2316f 100644
--- a/src/renderer/components/Login/AuthForm.tsx
+++ b/src/renderer/components/Login/AuthForm.tsx
@@ -3,8 +3,7 @@ import { Box, Button, TextField, Typography, InputAdornment, IconButton } from '
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import VisibilityIcon from '@mui/icons-material/Visibility';
import GradientTextField from '../GradientTextField';
-import GradientVisibilityToggleIcon from '../Icons/GradientVisibilityToggleIcon'
-import GradientVisibilityIcon from '../Icons/GradientVisibilityIcon'
+import GradientVisibilityToggleIcon from '../../assets/Icons/GradientVisibilityToggleIcon'
interface AuthFormProps {
config: {
diff --git a/src/renderer/components/Login/MemorySlider.tsx b/src/renderer/components/Login/MemorySlider.tsx
index 4911179..30f94c1 100644
--- a/src/renderer/components/Login/MemorySlider.tsx
+++ b/src/renderer/components/Login/MemorySlider.tsx
@@ -1,25 +1,187 @@
-import { Slider } from '@mui/material';
+import React from 'react';
+import { Box, Slider, Typography } from '@mui/material';
interface MemorySliderProps {
memory: number;
onChange: (e: Event, value: number | number[]) => void;
+ min?: number;
+ max?: number;
+ step?: number;
}
-const MemorySlider = ({ memory, onChange }: MemorySliderProps) => {
+const gradientPrimary =
+ 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)';
+
+const formatMb = (v: number) => `${v} MB`;
+const formatGb = (v: number) => `${(v / 1024).toFixed(v % 1024 === 0 ? 0 : 1)} GB`;
+
+const MemorySlider = ({
+ memory,
+ onChange,
+ min = 1024,
+ max = 32768,
+ step = 1024,
+}: MemorySliderProps) => {
+ // marks только на “красивых” значениях, чтобы не было каши
+ const marks = [
+ { value: 1024, label: '1 GB' },
+ { value: 4096, label: '4 GB' },
+ { value: 8192, label: '8 GB' },
+ { value: 16384, label: '16 GB' },
+ { value: 32768, label: '32 GB' },
+ ].filter((m) => m.value >= min && m.value <= max);
+
return (
-
+
+ {/* Header */}
+
+
+ Память
+
+
+
+ {memory >= 1024 ? formatGb(memory) : formatMb(memory)}
+
+
+
+ (v >= 1024 ? formatGb(v as number) : formatMb(v as number))}
+ shiftStep={step}
+ step={step}
+ marks={marks}
+ min={min}
+ max={max}
+ value={memory}
+ onChange={onChange}
+ sx={{
+ px: '0.2vw',
+
+ // rail (фон полосы)
+ '& .MuiSlider-rail': {
+ opacity: 1,
+ height: '0.9vh',
+ borderRadius: '999vw',
+ backgroundColor: 'rgba(255,255,255,0.10)',
+ boxShadow: 'inset 0 0.25vh 0.6vh rgba(0,0,0,0.45)',
+ },
+
+ // track (заполненная часть)
+ '& .MuiSlider-track': {
+ height: '0.9vh',
+ borderRadius: '999vw',
+ border: 'none',
+ background: gradientPrimary,
+ boxShadow: '0 0.6vh 1.6vh rgba(233,64,205,0.18)',
+ },
+
+ // thumb (ползунок)
+ '& .MuiSlider-thumb': {
+ width: '1.6vw',
+ height: '1.6vw',
+ minWidth: 14,
+ minHeight: 14,
+ borderRadius: '999vw',
+ background: 'rgba(10,10,20,0.92)',
+ border: '0.22vw solid rgba(255,255,255,0.18)',
+ boxShadow:
+ '0 0.9vh 2.4vh rgba(0,0,0,0.55), 0 0 1.2vw rgba(242,113,33,0.20)',
+ transition: 'transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease',
+ '&:hover': {
+ // transform: 'scale(1.06)',
+ borderColor: 'rgba(242,113,33,0.55)',
+ boxShadow:
+ '0 1.1vh 2.8vh rgba(0,0,0,0.62), 0 0 1.6vw rgba(233,64,205,0.28)',
+ },
+
+ // внутренний “свет”
+ '&:before': {
+ content: '""',
+ position: 'absolute',
+ inset: '18%',
+ borderRadius: '999vw',
+ background: gradientPrimary,
+ opacity: 0.85,
+ filter: 'blur(0.3vw)',
+ },
+ },
+
+ // value label (плашка значения)
+ '& .MuiSlider-valueLabel': {
+ fontFamily: 'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
+ fontSize: '0.85vw',
+ borderRadius: '1.2vw',
+ padding: '0.4vh 0.8vw',
+ color: '#fff',
+ background: 'rgba(0,0,0,0.55)',
+ border: '1px solid rgba(255,255,255,0.10)',
+ backdropFilter: 'blur(10px)',
+ boxShadow: '0 1.2vh 3vh rgba(0,0,0,0.55)',
+ '&:before': { display: 'none' },
+ },
+
+ // marks (точки)
+ '& .MuiSlider-mark': {
+ width: '0.35vw',
+ height: '0.35vw',
+ minWidth: 4,
+ minHeight: 4,
+ borderRadius: '999vw',
+ backgroundColor: 'rgba(255,255,255,0.18)',
+ },
+ '& .MuiSlider-markActive': {
+ backgroundColor: 'rgba(255,255,255,0.55)',
+ },
+
+ // mark labels (подписи)
+ '& .MuiSlider-markLabel': {
+ color: 'rgba(255,255,255,0.55)',
+ fontSize: '0.75vw',
+ marginTop: '1vh',
+ userSelect: 'none',
+ },
+
+ // focus outline
+ '& .MuiSlider-thumb.Mui-focusVisible': {
+ outline: 'none',
+ boxShadow:
+ '0 0 0 0.25vw rgba(242,113,33,0.20), 0 1.1vh 2.8vh rgba(0,0,0,0.62)',
+ },
+ }}
+ />
+
+ {/* Subtext */}
+
+ Шаг: {formatGb(step)} • Рекомендуем: 4–8 GB для большинства сборок
+
+
);
};
diff --git a/src/renderer/components/Settings/SettingsModal.tsx b/src/renderer/components/Settings/SettingsModal.tsx
index 71264b2..ca98873 100644
--- a/src/renderer/components/Settings/SettingsModal.tsx
+++ b/src/renderer/components/Settings/SettingsModal.tsx
@@ -10,7 +10,7 @@ interface SettingsModalProps {
memory: number;
preserveFiles: string[];
};
- onConfigChange: (newConfig: {
+ onConfigChange: (updater: (prev: { memory: number; preserveFiles: string[] }) => {
memory: number;
preserveFiles: string[];
}) => void;
@@ -58,7 +58,7 @@ const SettingsModal = ({
packName={packName}
initialSelected={config.preserveFiles}
onSelectionChange={(selected) => {
- onConfigChange({ ...config, preserveFiles: selected });
+ onConfigChange((prev) => ({ ...prev, preserveFiles: selected }));
}}
/>
@@ -66,8 +66,9 @@ const SettingsModal = ({
{
- onConfigChange({ ...config, memory: value as number });
+ onChange={(_, value) => {
+ const next = Array.isArray(value) ? value[0] : value;
+ onConfigChange((prev) => ({ ...prev, memory: next }));
}}
/>