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 })); }} />