diff --git a/public/images/dowdelogo.png b/public/images/dowdelogo.png new file mode 100644 index 0000000..483980f Binary files /dev/null and b/public/images/dowdelogo.png differ diff --git a/src/App.tsx b/src/App.tsx index 9d71cf6..fa249d9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,14 +4,16 @@ import { AppBar, Box, Container, - createTheme, - ThemeProvider, + IconButton, Toolbar, Typography, - useTheme } from "@mui/material"; +import { useNavigate, BrowserRouter } from "react-router-dom"; import { MyRoutes } from "./Routes"; import { styled } from '@mui/material/styles'; +import { ThemeProvider as CustomThemeProvider, useThemeContext } from './context/ThemeContext'; +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import LightModeIcon from '@mui/icons-material/LightMode'; // Styled components const StyledAppBar = styled(AppBar)(({ theme }) => ({ @@ -20,12 +22,20 @@ const StyledAppBar = styled(AppBar)(({ theme }) => ({ borderBottom: '1px solid rgba(255, 255, 255, 0.1)', })); +const StyledAppBarLight = styled(AppBar)(({ theme }) => ({ + background: 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 50%, #e0e0e0 100%)', + boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)', + borderBottom: '1px solid rgba(0, 0, 0, 0.1)', +})); + const LogoText = styled(Typography)(({ theme }) => ({ fontSize: '1.5rem', fontWeight: 800, - background: 'linear-gradient(135deg, #e94560 0%, #ff6b6b 100%)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(135deg, #dee2e6 0%, #cccccc 100%)' + : 'linear-gradient(135deg, #1a1a2e 0%, #333333 100%)', WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', + WebkitTextFillColor: theme.palette.mode === 'dark' ? '#dee2e6' : '#1a1a2e', letterSpacing: '-0.5px', cursor: 'pointer', '&:hover': { @@ -34,69 +44,79 @@ const LogoText = styled(Typography)(({ theme }) => ({ }, })); -function App() { - const theme = useTheme(); +const ThemeToggle = styled(IconButton)(({ theme }) => ({ + color: 'inherit', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + }, +})); - const themeConfig = createTheme({ - breakpoints: { - values: { - xs: 0, - sm: 600, - md: 900, - lg: 1320, - xl: 1536, - }, - }, - palette: { - primary: { - main: "#1a1a2e", - }, - background: { - default: '#0a0a0a', - paper: '#1a1a2e', - }, - }, - typography: { - fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif', - }, - shape: { - borderRadius: 12, - }, - components: { - MuiTableCell: { - styleOverrides: { - root: { - color: 'rgba(255, 255, 255, 0.85)', - }, - }, - }, - }, - }); +const ThemeToggleLight = styled(IconButton)(({ theme }) => ({ + color: 'inherit', + '&:hover': { + backgroundColor: 'rgba(0, 0, 0, 0.05)', + }, +})); + +function AppBarContent() { + const { mode, toggleTheme } = useThemeContext(); + const navigate = useNavigate(); + const isDark = mode === 'dark'; + + const handleLogoClick = () => navigate('/'); return ( - - - + <> + {isDark ? ( - {/* Left side - Logo */} - - 🎮 Autogenerated wiki + logo + + Autogenerated wiki + + + - - - - - - - + ) : ( + + + + + logo + + Autogenerated wiki + + + + + + + + + )} + ); } -export default App; \ No newline at end of file +function App() { + return ( + + + + + + + + + + + ); +} + +export default App; diff --git a/src/Routes.tsx b/src/Routes.tsx index 4a25525..4e60614 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -1,4 +1,4 @@ -import {Routes, Route, BrowserRouter} from "react-router-dom"; +import {Routes, Route} from "react-router-dom"; import ModsPage from "./pages/ModsPage"; import ModPage from "./pages/ModPage"; import RacePageFast from "./pages/RacePageFast"; @@ -8,14 +8,12 @@ import BuildingPage from "./pages/BuildingPage"; export const MyRoutes = () => { return( - - + }/> }/> }/> }/> }/> - ) } \ No newline at end of file diff --git a/src/classes/Ability.tsx b/src/classes/Ability.tsx index 47c0aca..231dddf 100644 --- a/src/classes/Ability.tsx +++ b/src/classes/Ability.tsx @@ -20,18 +20,18 @@ function Ability(props: IAbilityProps){ return
} + sx={{color: '#dee2e6', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} + expandIcon={} aria-controls="panel1-content" - sx={{color: '#ffffff'}} > - + {ability.activationType === "Passive ability" ? : }   {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')} ({ability.activationType}) - +
diff --git a/src/classes/AbilityFull.tsx b/src/classes/AbilityFull.tsx index 4bf0e73..e37eee0 100644 --- a/src/classes/AbilityFull.tsx +++ b/src/classes/AbilityFull.tsx @@ -5,7 +5,6 @@ import ArmorTypeNames from "../types/ArmorTypeValues"; import { AccordionDetails, Grid2, - Paper, styled, Table, TableBody, @@ -28,6 +27,7 @@ import ability from "./Ability"; import AbilitySpawnedObject from "./AbilitySpawnedObject"; import DpsTable from "./DpsTable"; import {DescriptionBox} from "../commons/DescriptionBox"; +import {StyledPaper} from "../commons/StyledPaper"; interface IAbilityFullState { ability?: IAbilityFill; @@ -42,36 +42,22 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: 'rgba(233, 69, 96, 0.15)', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.15)', fontWeight: 700, marginRight: 'auto', marginLeft: 'auto', paddingLeft: 10, - borderBottom: '2px solid rgba(233, 69, 96, 0.3)', + borderBottom: `2px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)'}`, }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', - color: 'rgba(255, 255, 255, 0.85)', + color: '#dee2e6', paddingRight: 18, paddingLeft: 10, }, })); - const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, - })); - useEffect(() => { if(props.abilityFull !== undefined){ @@ -108,11 +94,11 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race const showTargetTable = !showDpsTable && ability.abilityEnvironment == null && (ability.targetFilter.length > 0 || ability.radius !== 0) return ( -
+
{ability.initialDelayTime !== undefined && props.isChild === true && -
 After {ability.initialDelayTime.toFixed(1)}s  +
 After {ability.initialDelayTime.toFixed(1)}s 
} @@ -215,7 +201,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race {showDpsTable ? - {(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && Every {ability.refreshTime.toFixed(1)}s deal damege:} + {(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && Every {ability.refreshTime.toFixed(1)}s deal damege:} : showTargetTable ? diff --git a/src/classes/AbilitySpawnedObject.tsx b/src/classes/AbilitySpawnedObject.tsx index 93ecbda..46eb817 100644 --- a/src/classes/AbilitySpawnedObject.tsx +++ b/src/classes/AbilitySpawnedObject.tsx @@ -1,7 +1,7 @@ import { Accordion, AccordionDetails, - AccordionSummary, Grid2, Paper, + AccordionSummary, Grid2, Table, TableBody, TableCell, TableContainer, @@ -24,6 +24,7 @@ import Ability from "./Ability"; import DeathExplosion from "./DeathExplosion"; import {styled} from '@mui/material/styles'; import {DescriptionBox} from "../commons/DescriptionBox"; +import {StyledPaper} from "../commons/StyledPaper"; const SectionTitle = styled(Typography)(({ theme }) => ({ fontWeight: 700, @@ -31,20 +32,6 @@ const SectionTitle = styled(Typography)(({ theme }) => ({ fontSize: '1.25rem', })); -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, -})); - interface AbilityEnvironmentProps { abilityEnvironment: IAbilityEnvironment, @@ -56,7 +43,7 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) { const abilityEnvironment = props.abilityEnvironment - return
+ return
{props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')} (Spawned object) diff --git a/src/classes/DeathExplosion.tsx b/src/classes/DeathExplosion.tsx index 4ffa2b0..69a29ea 100644 --- a/src/classes/DeathExplosion.tsx +++ b/src/classes/DeathExplosion.tsx @@ -1,6 +1,6 @@ import { AccordionDetails, - AccordionSummary, Grid2, Paper, + AccordionSummary, Grid2, Table, TableBody, TableCell, TableContainer, TableHead, @@ -24,22 +24,9 @@ import Required from "./Required"; import ArmorTypeNames from "../types/ArmorTypeValues"; import DpsTable from "./DpsTable"; import {StyledAccordion} from "../commons/StyledAccordion"; +import {StyledPaper} from "../commons/StyledPaper"; import {styled} from "@mui/material/styles"; -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'rgba(255, 255, 255, 0.03)', - border: '1px solid rgba(255, 255, 255, 0.05)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.85)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, -})); - interface DeathExpProps { deathExplosion: IDeathExplosion, @@ -54,15 +41,15 @@ function DeathExplosion(props: DeathExpProps) { return chanceTotalNotZero ?
} + sx={{color: '#dee2e6', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} + expandIcon={} aria-controls="panel1-content" - sx={{color: '#ffffff'}} > - + Death Explosion - + diff --git a/src/classes/DpsTable.tsx b/src/classes/DpsTable.tsx index d872f02..275844a 100644 --- a/src/classes/DpsTable.tsx +++ b/src/classes/DpsTable.tsx @@ -19,17 +19,17 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: 'rgba(233, 69, 96, 0.15)', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.15)', fontWeight: 700, marginRight: 'auto', marginLeft: 'auto', paddingLeft: 10, - borderBottom: '2px solid rgba(233, 69, 96, 0.3)', + borderBottom: `2px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)'}`, }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', - color: 'rgba(255, 255, 255, 0.85)', + color: '#dee2e6', paddingRight: 18, paddingLeft: 10, }, diff --git a/src/classes/ModifiersProvideTable.tsx b/src/classes/ModifiersProvideTable.tsx index b73e353..374c859 100644 --- a/src/classes/ModifiersProvideTable.tsx +++ b/src/classes/ModifiersProvideTable.tsx @@ -8,17 +8,7 @@ import {IMod} from "../types/Imod"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import {styled} from '@mui/material/styles'; import {StyledLink} from "../commons/StyledLink"; - -// Styled components for dark theme -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.85)', - }, -})); +import {StyledPaper} from "../commons/StyledPaper"; interface IModifiersProvidesTable { @@ -162,8 +152,8 @@ export function ModifiersProvidesAddonTable(props: IModifiersProvidesTable) { - {getModName(m.reference)} - {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} + {getModName(m.reference)} + {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} )} @@ -247,9 +237,9 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) { - {getTarget(m.target)} - {getModName(m.reference)} - {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} + {getTarget(m.target)} + {getModName(m.reference)} + {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} {m.maxLifeTime !== undefined && m.maxLifeTime > 0 && {m.maxLifeTime}s } diff --git a/src/classes/Required.tsx b/src/classes/Required.tsx index ea29d11..0a0663e 100644 --- a/src/classes/Required.tsx +++ b/src/classes/Required.tsx @@ -115,20 +115,20 @@ function Required (props: IRequiredProps) { return ( -
-

Required:

+
+

Required:

{requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null && -
  Population:   Population:   {requirement.requiredTotalPop}
} {requirement.mobBonus !== null && requirement.mobBonus !== undefined && -
  Mob requirement:   Mob requirement:   {requirement.mobBonus.mobvalueRequired} in radius {requirement.mobBonus.proximityRequired}
} {requirement.requireAddon !== null && -
  Addon:   Addon:   {requirement.requireAddon.name} @@ -137,26 +137,26 @@ function Required (props: IRequiredProps) {
} {requirement.requirementAddonExclusive != null && -
{renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}
} +
{renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}
} {requirement.requirementBuildings.map(b => -
  Building:   Building:   {b.name}
) } {requirement.requirementResearches.length !== 0 && -
{renderRequirementResearches(requirement.requirementResearches,)}
} +
{renderRequirementResearches(requirement.requirementResearches,)}
} {requirement.requirementSquads.length !== 0 && -
{renderRequirementSquads(requirement.requirementSquads,)}
} +
{renderRequirementSquads(requirement.requirementSquads,)}
} {requirement.requirementResearchesEither.length !== 0 && -
{renderRequirementResearchesEither(requirement.requirementResearchesEither,)}
} +
{renderRequirementResearchesEither(requirement.requirementResearchesEither,)}
} {requirement.requirementBuildingsEither.length !== 0 && -
{renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}
} +
{renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}
} {requirement.requirementStructureExclusive != null && -
{renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}
} - {requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null &&
{renderLimitPerBuilding(requirement.limitByBuilding)}
} +
{renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}
} + {requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null &&
{renderLimitPerBuilding(requirement.limitByBuilding)}
} {requirement.requirementsGlobalAddons.length !== 0 && -
{renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}
} -
{renderRequirementOwnership(requirement.requiredOwnership)}
+
{renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}
} +
{renderRequirementOwnership(requirement.requiredOwnership)}
) } diff --git a/src/classes/Sergeant.tsx b/src/classes/Sergeant.tsx index b22f22a..f9a221b 100644 --- a/src/classes/Sergeant.tsx +++ b/src/classes/Sergeant.tsx @@ -1,6 +1,6 @@ import React from "react"; import {ISergeant, IShortWeapon} from "../types/IUnit"; -import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip, Typography} from "@mui/material"; +import {Grid2, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip, Typography} from "@mui/material"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import ArmorType from "./ArmorType"; import WeaponSlot from "./WeaponSlot"; @@ -14,20 +14,7 @@ import Ability from "./Ability"; import DeathExplosion from "./DeathExplosion"; import {styled} from '@mui/material/styles'; import {DescriptionBox} from "../commons/DescriptionBox"; - -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, -})); +import {StyledPaper} from "../commons/StyledPaper"; const SectionTitle = styled(Typography)(({ theme }) => ({ fontWeight: 700, @@ -60,7 +47,7 @@ const Sergeant = (props: SergeantProps) => { }) return ( -
+
diff --git a/src/classes/UnitsTable.tsx b/src/classes/UnitsTable.tsx index 26cb8c5..74af101 100644 --- a/src/classes/UnitsTable.tsx +++ b/src/classes/UnitsTable.tsx @@ -5,30 +5,22 @@ import {styled} from '@mui/material/styles'; import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; import {StyledLink} from "../commons/StyledLink"; - - -// Styled components for dark theme -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', -})); +import {StyledPaper} from "../commons/StyledPaper"; const StyledTable = styled(Table)(({ theme }) => ({ '& .MuiTableCell-head': { - backgroundColor: 'rgba(233, 69, 96, 0.15)', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.15)', fontWeight: 700, fontSize: '1rem', - borderBottom: '2px solid rgba(233, 69, 96, 0.3)', + borderBottom: `2px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)'}`, }, '& .MuiTableCell-body': { - color: 'rgba(255, 255, 255, 0.85)', - borderBottom: '1px solid rgba(255, 255, 255, 0.05)', + color: theme.palette.text.primary, + borderBottom: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'}`, fontSize: '0.9rem', }, '& .MuiTableRow-hover:hover': { - backgroundColor: 'rgba(233, 69, 96, 0.08)', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(233, 69, 96, 0.08)', }, })); @@ -36,7 +28,7 @@ const StyledHeaderLink = styled(StyledLink)(({ theme }) => ({ fontWeight: 700, fontSize: '1.1rem', '&:hover': { - color: '#e94560', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560', }, })); diff --git a/src/classes/Weapon.tsx b/src/classes/Weapon.tsx index 6cd014f..b1bef73 100644 --- a/src/classes/Weapon.tsx +++ b/src/classes/Weapon.tsx @@ -37,21 +37,21 @@ class Weapon extends React.Component { return (
} + expandIcon={} aria-controls="panel1-content" id="panel1-header" - sx={{color: '#ffffff'}} + sx={{color: '#dee2e6', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} > - {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ? + {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ? : ( weapon.isMeleeWeapon ? : - )} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} + )}   {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} {this.props.isDefault && (default)} - +
diff --git a/src/classes/WeaponFull.tsx b/src/classes/WeaponFull.tsx index a5b257b..25d3243 100644 --- a/src/classes/WeaponFull.tsx +++ b/src/classes/WeaponFull.tsx @@ -5,7 +5,6 @@ import ArmorTypeNames from "../types/ArmorTypeValues"; import { AccordionDetails, Grid2, - Paper, styled, Table, TableBody, @@ -23,6 +22,7 @@ import {IRaceUnits} from "../types/IUnitShort"; import {IRaceBuildings} from "../types/IBuildingShort"; import {ModifiersProvidesTable} from "./ModifiersProvideTable"; import {DescriptionBox} from "../commons/DescriptionBox"; +import {StyledPaper} from "../commons/StyledPaper"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; interface IWeaponFull { @@ -39,35 +39,21 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: 'rgba(233, 69, 96, 0.15)', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.15)', marginRight: 'auto', marginLeft: 'auto', paddingLeft: 10, - borderBottom: '2px solid rgba(233, 69, 96, 0.3)', + borderBottom: `2px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)'}`, }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', - color: 'rgba(255, 255, 255, 0.85)', + color: '#dee2e6', paddingRight: 18, paddingLeft: 10, }, })); - const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, - })); - useEffect(() => { fetch(WeaponUrl + "/" + props.mod.id + "/" + props.weaponId) @@ -151,7 +137,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, return ( -
+
{!props.isDefault && @@ -262,8 +248,8 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, onChange={handleChange} sx={{ mb: 2 }} > - Dps - One hit average damage + Dps + One hit average damage { props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? @@ -327,7 +313,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, name={ArmorTypeNames.BuildingSuper}/> -
+
Morale
@@ -381,7 +367,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, name={ArmorTypeNames.BuildingHigh}/> -
+
Morale
@@ -413,7 +399,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, {weapon.modifiers.length > 0 && -

Modifiers

+

Modifiers

} {weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu && diff --git a/src/classes/building/BuildingAddon.tsx b/src/classes/building/BuildingAddon.tsx index fccf102..09f0edb 100644 --- a/src/classes/building/BuildingAddon.tsx +++ b/src/classes/building/BuildingAddon.tsx @@ -3,7 +3,6 @@ import { AccordionDetails, AccordionSummary, Grid2, - Paper, Table, TableBody, TableCell, @@ -19,24 +18,10 @@ import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable"; import Required from "../Required"; import {StyledAccordion} from "../../commons/StyledAccordion"; import {DescriptionBox} from "../../commons/DescriptionBox"; +import {StyledPaper} from "../../commons/StyledPaper"; import {styled} from "@mui/material/styles"; - -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'rgba(255, 255, 255, 0.03)', - border: '1px solid rgba(255, 255, 255, 0.05)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.85)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, -})); - interface IBuildingAddonProps { addon: IBuildingAddon, building: IBuilding, @@ -52,16 +37,16 @@ function BuildingAddon(props: IBuildingAddonProps){ return
} + sx={{color: '#dee2e6', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} + expandIcon={} aria-controls="panel1-content" - sx={{color: '#ffffff'}} > - +   {addon.name} - + diff --git a/src/classes/building/Research.tsx b/src/classes/building/Research.tsx index caa1483..761e151 100644 --- a/src/classes/building/Research.tsx +++ b/src/classes/building/Research.tsx @@ -23,10 +23,10 @@ import {StyledAccordion} from "../../commons/StyledAccordion"; import {styled} from "@mui/material/styles"; const StyledLink = styled('a')(({ theme }) => ({ - color: '#e94560', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560', textDecoration: 'none', '&:hover': { - color: '#ff6b6b', + color: theme.palette.mode === 'dark' ? '#cccccc' : '#ff6b6b', textDecoration: 'underline', }, })); @@ -45,16 +45,16 @@ function Research(props: IResearchProps){ return
} + sx={{color: '#dee2e6', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} + expandIcon={} aria-controls="panel1-content" - sx={{color: '#ffffff'}} > - +   {research.name} - +
@@ -71,7 +71,7 @@ export function renderAffectedResearches(researches: IResearchShort[], modId: nu } - return
{researches.map(rs => + return
{researches.map(rs => rs.buildingId == null ? : Research affect: {researchLink(rs)}
)}
diff --git a/src/classes/building/ResearchFull.tsx b/src/classes/building/ResearchFull.tsx index d4b8272..1c4c813 100644 --- a/src/classes/building/ResearchFull.tsx +++ b/src/classes/building/ResearchFull.tsx @@ -5,7 +5,6 @@ import { AccordionDetails, AccordionSummary, Grid2, - Paper, Table, TableBody, TableCell, @@ -24,20 +23,7 @@ import {ResearchUrl, WeaponUrl} from "../../core/api"; import {IWeapon} from "../../types/IUnit"; import {styled} from '@mui/material/styles'; import {DescriptionBox} from "../../commons/DescriptionBox"; - -const StyledPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', - borderRadius: '12px', - overflow: 'hidden', - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', - }, - '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', - fontWeight: 600, - }, -})); +import {StyledPaper} from "../../commons/StyledPaper"; interface IResearchFull { diff --git a/src/commons/BackButton.tsx b/src/commons/BackButton.tsx index 8d99c39..c09cb11 100644 --- a/src/commons/BackButton.tsx +++ b/src/commons/BackButton.tsx @@ -3,12 +3,12 @@ import {styled} from '@mui/material/styles'; export const BackButton = styled(Button)(({ theme }) => ({ marginBottom: theme.spacing(3), - color: '#e94560BB', - borderColor: 'rgba(233, 69, 96, 0.5)', + color: '#FFD700', + borderColor: 'rgba(255, 215, 0, 0.5)', textTransform: 'none', fontWeight: 600, '&:hover': { - borderColor: '#e94560', - backgroundColor: 'rgba(233, 69, 96, 0.1)', + borderColor: '#FFD700', + backgroundColor: 'rgba(255, 215, 0, 0.1)', }, })); \ No newline at end of file diff --git a/src/commons/DescriptionBox.tsx b/src/commons/DescriptionBox.tsx index 0ad4309..c6316d4 100644 --- a/src/commons/DescriptionBox.tsx +++ b/src/commons/DescriptionBox.tsx @@ -1,12 +1,12 @@ import {styled} from '@mui/material/styles'; export const DescriptionBox = styled('div')(({ theme }) => ({ - color: 'rgba(255, 255, 255, 0.85)', + color: theme.palette.text.primary, lineHeight: 1.7, fontSize: '0.95rem', padding: theme.spacing(3), - background: 'rgba(255, 255, 255, 0.03)', + background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.03)', borderRadius: '12px', - border: '1px solid rgba(255, 255, 255, 0.05)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'}`, whiteSpace: 'pre-wrap', })); \ No newline at end of file diff --git a/src/commons/StyledAccordion.tsx b/src/commons/StyledAccordion.tsx index 2f7b592..86f497d 100644 --- a/src/commons/StyledAccordion.tsx +++ b/src/commons/StyledAccordion.tsx @@ -2,8 +2,10 @@ import {Accordion} from '@mui/material'; import {styled} from '@mui/material/styles'; export const StyledAccordion = styled(Accordion)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '12px', '&:before': { display: 'none' }, '&.Mui-expanded': { margin: '0 0 8px 0' }, diff --git a/src/commons/StyledLink.tsx b/src/commons/StyledLink.tsx index 8343828..68da5c3 100644 --- a/src/commons/StyledLink.tsx +++ b/src/commons/StyledLink.tsx @@ -2,11 +2,11 @@ import {Link} from "@mui/material"; import {styled} from '@mui/material/styles'; export const StyledLink = styled(Link)(({ theme }) => ({ - color: '#e94560BB', + color: theme.palette.mode === 'dark' ? '#abc3ff' : '#e94560BB', textDecoration: 'none', transition: 'all 0.2s ease', '&:hover': { - color: '#ff6b6bBB', + color: theme.palette.mode === 'dark' ? '#ffffff' : '#ff6b6bBB', textDecoration: 'underline', }, })); diff --git a/src/commons/StyledPaper.tsx b/src/commons/StyledPaper.tsx new file mode 100644 index 0000000..a593891 --- /dev/null +++ b/src/commons/StyledPaper.tsx @@ -0,0 +1,19 @@ +import {Paper} from '@mui/material'; +import {styled} from '@mui/material/styles'; + +export const StyledPaper = styled(Paper)(({ theme }) => ({ + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: theme.palette.text.primary, + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560', + fontWeight: 600, + }, +})); + diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx new file mode 100644 index 0000000..a75774a --- /dev/null +++ b/src/context/ThemeContext.tsx @@ -0,0 +1,48 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { darkTheme, lightTheme } from '../themes/theme'; + +type ThemeMode = 'dark' | 'light'; + +interface ThemeContextType { + mode: ThemeMode; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + + +export const ThemeProvider = ({ children }: { children: ReactNode }) => { + const [mode, setMode] = useState(() => { + const saved = localStorage.getItem('theme'); + return (saved === 'light' ? 'light' : 'dark') as ThemeMode; + }); + + useEffect(() => { + localStorage.setItem('theme', mode); + }, [mode]); + + const toggleTheme = () => { + setMode(prev => prev === 'dark' ? 'light' : 'dark'); + }; + + const theme = mode === 'dark' ? darkTheme : lightTheme; + + return ( + + + + {children} + + + ); +}; + +export const useThemeContext = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useThemeContext must be used within a ThemeProvider'); + } + return context; +}; diff --git a/src/core/api.js b/src/core/api.js index 292f2cf..17b4afe 100644 --- a/src/core/api.js +++ b/src/core/api.js @@ -1,4 +1,7 @@ +import React from 'react'; +import { useTheme } from '@mui/material/styles'; + export const UserUrl = process.env.REACT_APP_HOST_URL + '/api/v1/user'; export const WeaponUrl = process.env.REACT_APP_HOST_URL + '/api/v1/weapon'; export const AbilityUrl = process.env.REACT_APP_HOST_URL + '/api/v1/ability'; @@ -8,3 +11,11 @@ export const AvailableRacesPart = process.env.REACT_APP_HOST_URL + '/api/v1/race export const AvailableUnits = process.env.REACT_APP_HOST_URL + '/api/v1/units'; export const AvailableBuildings = process.env.REACT_APP_HOST_URL + '/api/v1/buildings'; export const IconUrl = process.env.REACT_APP_HOST_URL + '/api/v1/grapics/icon/'; + +export function withTheme(Component) { + return function WithTheme(props) { + const theme = useTheme(); + return ; + }; +} + diff --git a/src/index.css b/src/index.css index 02d3cde..ecc94a3 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,35 @@ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); +:root { + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a2e; + --bg-tertiary: #16213e; + --text-primary: #dee2e6; + --text-secondary: #dee2e6; + --text-muted: rgba(255, 255, 255, 0.5); + --border-color: rgba(255, 255, 255, 0.1); + --border-light: rgba(255, 255, 255, 0.05); + --card-bg: rgba(255, 255, 255, 0.03); + --accent: #dee2e6; + --accent-light: #cccccc; + --scrollbar-track: #1a1a2e; +} + +body.light-theme { + --bg-primary: #f5f5f5; + --bg-secondary: #ffffff; + --bg-tertiary: #e8e8e8; + --text-primary: rgba(0, 0, 0, 0.85); + --text-secondary: rgba(0, 0, 0, 0.6); + --text-muted: rgba(0, 0, 0, 0.5); + --border-color: rgba(0, 0, 0, 0.1); + --border-light: rgba(0, 0, 0, 0.05); + --card-bg: rgba(0, 0, 0, 0.03); + --accent: #e94560; + --accent-light: #ff6b6b; + --scrollbar-track: #e8e8e8; +} + body { margin: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', @@ -7,8 +37,12 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: #0a0a0a; - color: rgba(255, 255, 255, 0.85); + background-color: var(--bg-primary); + color: var(--text-primary); +} + +body.light-theme code { + background: rgba(0, 0, 0, 0.1); } code { @@ -21,7 +55,7 @@ code { * { scrollbar-width: thin; - scrollbar-color: #e94560 #1a1a2e; + scrollbar-color: var(--accent) var(--scrollbar-track); } *::-webkit-scrollbar { @@ -29,11 +63,11 @@ code { } *::-webkit-scrollbar-track { - background: #1a1a2e; + background: var(--scrollbar-track); } *::-webkit-scrollbar-thumb { - background-color: #e94560; + background-color: var(--accent); border-radius: 4px; } diff --git a/src/pages/BuildingPage.tsx b/src/pages/BuildingPage.tsx index 5a65273..a18bfbf 100644 --- a/src/pages/BuildingPage.tsx +++ b/src/pages/BuildingPage.tsx @@ -17,7 +17,7 @@ import { TableCell, TableRow, Tooltip, - Typography, LinearProgress + Typography, LinearProgress, Theme, } from "@mui/material"; import ArmorType from "../classes/ArmorType"; import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer'; @@ -34,6 +34,7 @@ import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import Ability from "../classes/Ability"; import DeathExplosion from "../classes/DeathExplosion"; import {styled} from '@mui/material/styles'; +import {withTheme} from "../core/api"; import {DescriptionBox} from "../commons/DescriptionBox"; import {ArrowBack} from "@mui/icons-material"; import {BackButton} from "../commons/BackButton"; @@ -46,51 +47,80 @@ const SectionTitle = styled(Typography)(({ theme }) => ({ })); const StatsPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '12px', overflow: 'hidden', '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { - color: 'rgba(255, 255, 255, 0.9)', + color: theme.palette.text.primary, }, '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560', fontWeight: 600, }, })); +const StyledDivider = styled(Divider)(({ theme }) => ({ + my: 3, + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', +})); + +const BuildingTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 800, + mb: 1, + display: 'flex', + alignItems: 'center', + gap: 2, +})); + +const BuildingSubtitle = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, + fontWeight: 500, +})); + +const UnitLink = styled(Link)(({ theme }) => ({ + color: theme.palette.text.primary, + textDecoration: 'none', + '&:hover': { color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560' }, +})); + +const UnitListItem = styled(ListItem)(({ theme }) => ({ + color: theme.palette.text.primary, + padding: '8px 12px', + background: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.03)', + borderRadius: '8px', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'}`, + transition: 'all 0.2s ease', + '&:hover': { background: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(233, 69, 96, 0.1)', borderColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.3)' : 'rgba(233, 69, 96, 0.3)' }, +})); + interface UintPageState { building: IBuilding, mod: IMod, } -function Unit (unit: IUnitShort, modId: number, raceId: String) { +function Unit (unit: IUnitShort, modId: number, raceId: String, theme: Theme) { return ( - - + + {unit.icon && } {unit.name} {unit.canDetect &&  } - - + + ) } -function Building(building: IBuilding, mod: IMod) { +function Building(building: IBuilding, mod: IMod, theme: Theme) { document.title = building.name + " — " + mod.name + const isDark = theme.palette.mode === 'dark'; let mapBuildingWeapons: Map> = new Map(); @@ -113,23 +143,14 @@ function Building(building: IBuilding, mod: IMod) { return ( - + {building.icon && } {buildingName} - - + + {mod.name} ({mod.version}) - + @@ -232,7 +253,7 @@ function Building(building: IBuilding, mod: IMod) { Unit production {building.units.map(unit => - Unit(unit, mod.id, building.race.id) + Unit(unit, mod.id, building.race.id, theme) )} } @@ -271,7 +292,7 @@ function Building(building: IBuilding, mod: IMod) { Hotkey: {building.hotkey} - + ) } @@ -298,9 +319,10 @@ class BuildingPage extends React.Component { if (this.state != null && this.state.building != null && this.state.mod != null) { const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId + const { theme } = this.props; return ( - + } @@ -308,7 +330,7 @@ class BuildingPage extends React.Component { > Back to race - {Building(this.state.building, this.state.mod)} + {Building(this.state.building, this.state.mod, theme)} ); } else { @@ -321,4 +343,4 @@ class BuildingPage extends React.Component { } } -export default withRouter(BuildingPage); +export default withRouter(withTheme(BuildingPage)); diff --git a/src/pages/ModPage.tsx b/src/pages/ModPage.tsx index 8fdf927..3b96c43 100644 --- a/src/pages/ModPage.tsx +++ b/src/pages/ModPage.tsx @@ -1,6 +1,7 @@ import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api"; import React, { useState } from "react"; import {withRouter} from "../core/withrouter"; +import {withTheme} from "../core/api"; import {IMod} from "../types/Imod"; import { ArrowBack, @@ -19,6 +20,12 @@ import UnitsTable from "../classes/UnitsTable"; import {BackButton} from "../commons/BackButton"; + +const ModNotFound = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + + interface ModPageState { mod: IMod | null, loading: boolean, @@ -60,6 +67,7 @@ class ModPage extends React.Component { if (this.state != null && this.state.mod != null) { document.title = this.state.mod.name + " — ModWiki"; + const isDark = this.props.theme.palette.mode === 'dark'; return ( @@ -71,12 +79,20 @@ class ModPage extends React.Component { Back to mods list - - {this.state.mod.name} ({this.state.mod.version}) + {this.state.mod.name} + + + ({this.state.mod.version}) @@ -87,9 +103,9 @@ class ModPage extends React.Component { } else { return ( - + Mod not found - + ); } @@ -98,4 +114,4 @@ class ModPage extends React.Component { -export default withRouter(ModPage); \ No newline at end of file +export default withRouter(withTheme(ModPage)); diff --git a/src/pages/ModsPage.tsx b/src/pages/ModsPage.tsx index 4eedae5..47fd8e7 100644 --- a/src/pages/ModsPage.tsx +++ b/src/pages/ModsPage.tsx @@ -23,7 +23,9 @@ import { styled } from '@mui/material/styles'; const PageHeader = styled(Paper)(({ theme }) => ({ padding: theme.spacing(6, 4), marginBottom: theme.spacing(6), - background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)' + : 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 50%, #e0e0e0 100%)', borderRadius: '16px', position: 'relative', overflow: 'hidden', @@ -34,7 +36,7 @@ const PageHeader = styled(Paper)(({ theme }) => ({ left: 0, right: 0, bottom: 0, - background: 'radial-gradient(circle at 30% 50%, rgba(233, 69, 96, 0.1) 0%, transparent 50%)', + background: 'radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.05) 0%, #dee2e6 50%)', pointerEvents: 'none', }, })); @@ -43,8 +45,10 @@ const ModCard = styled(Card)(({ theme }) => ({ height: '100%', display: 'flex', flexDirection: 'column', - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '16px', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', position: 'relative', @@ -56,14 +60,15 @@ const ModCard = styled(Card)(({ theme }) => ({ left: 0, right: 0, height: '3px', - background: 'linear-gradient(90deg, #e94560 0%, #ff6b6b 100%)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(90deg, #dee2e6 0%, #cccccc 100%)' + : 'linear-gradient(90deg, #e94560 0%, #ff6b6b 100%)', transform: 'scaleX(0)', transition: 'transform 0.3s ease', }, '&:hover': { - transform: 'translateY(-8px)', - boxShadow: '0 20px 40px rgba(233, 69, 96, 0.2)', - borderColor: 'rgba(233, 69, 96, 0.3)', + boxShadow: theme.palette.mode === 'dark' ? '0 20px 40px rgba(255, 255, 255, 0.1)' : '0 20px 40px rgba(233, 69, 96, 0.2)', + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)', '&::before': { transform: 'scaleX(1)', }, @@ -75,16 +80,79 @@ const VersionLink = styled(NavLink)(({ theme }) => ({ alignItems: 'center', padding: theme.spacing(1.5, 2), marginBottom: theme.spacing(1), - background: 'rgba(255, 255, 255, 0.05)', + background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)', borderRadius: '10px', textDecoration: 'none', color: 'inherit', transition: 'all 0.2s ease', - border: '1px solid rgba(255, 255, 255, 0.1)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, '&:hover': { - background: 'rgba(233, 69, 96, 0.15)', - borderColor: 'rgba(233, 69, 96, 0.3)', - transform: 'translateX(4px)', + background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.15)', + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)', + }, +})); + +const ModTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 1, + background: theme.palette.mode === 'dark' + ? 'linear-gradient(135deg, #dee2e6 0%, #e0e0e0 100%)' + : 'linear-gradient(135deg, #000000 0%, #333333 100%)', + WebkitBackgroundClip: theme.palette.mode === 'dark' ? 'text' : 'initial', + WebkitTextFillColor: theme.palette.mode === 'dark' ? '#dee2e6' : 'initial', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#000000', +})); + +const ModDescription = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, + lineHeight: 1.6, +})); + +const VersionsChip = styled(Chip)(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(233, 69, 96, 0.2)', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#ff6b6b', + fontWeight: 600, +})); + +const StyledDivider = styled(Divider)(({ theme }) => ({ + my: 2, + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', +})); + +const MainVersionsLabel = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.primary, + fontWeight: 600, + mb: 1.5, +})); + +const VersionText = styled(Typography)(({ theme }) => ({ + fontWeight: 600, + color: theme.palette.text.secondary, +})); + +const VersionArrow = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + +const BetaVersionsLabel = styled(Typography)(({ theme }) => ({ + color: 'rgba(255, 193, 7, 0.9)', + fontWeight: 600, + mb: 1, +})); + +const OpenLatestButton = styled(Button)(({ theme }) => ({ + width: '100%', + color: '#FFD700', + borderColor: 'rgba(255, 215, 0, 0.5)', + fontWeight: 600, + borderRadius: '10px', + textTransform: 'none', + fontSize: '1rem', + py: 1.5, + '&:hover': { + borderColor: '#FFD700', + backgroundColor: 'rgba(255, 215, 0, 0.1)', + boxShadow: '0 8px 20px rgba(255, 215, 0, 0.2)', }, })); @@ -128,88 +196,48 @@ function Mods({ mods }: ModsProps) { const { latest, hasBeta, betaVersion } = getLatestVersion(modName); return ( - + - + {modName} - - - + - - {hasBeta && ( - - )} - - - - Main versions: - - {sameMods.filter(m => !m.isBeta).map(mod => ( - + Version {mod.version} - - + + → - + ))} {hasBeta && ( - + Beta versions: - + {betaVersion && ( Version {betaVersion.version} (Beta) - + → - + )} @@ -226,18 +254,17 @@ function Mods({ mods }: ModsProps) { state={latest?.id} sx={{ width: '100%', - color: '#e94560', - borderColor: 'rgba(233, 69, 96, 0.5)', + color: '#FFD700', + borderColor: 'rgba(255, 215, 0, 0.5)', fontWeight: 600, borderRadius: '10px', textTransform: 'none', fontSize: '1rem', py: 1.5, '&:hover': { - borderColor: '#e94560', - backgroundColor: 'rgba(233, 69, 96, 0.1)', - transform: 'translateY(-2px)', - boxShadow: '0 8px 20px rgba(233, 69, 96, 0.2)', + borderColor: '#FFD700', + backgroundColor: 'rgba(255, 215, 0, 0.1)', + boxShadow: '0 8px 20px rgba(255, 215, 0, 0.2)', }, }} > diff --git a/src/pages/RacePageFast.tsx b/src/pages/RacePageFast.tsx index 48d81d0..c422fd8 100644 --- a/src/pages/RacePageFast.tsx +++ b/src/pages/RacePageFast.tsx @@ -1,5 +1,5 @@ import {AvailableBuildings, AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api"; -import React, {useState} from "react"; +import React from "react"; import {withRouter} from "../core/withrouter"; import {IMod} from "../types/Imod"; import {Irace} from "../types/Irace"; @@ -18,11 +18,13 @@ import { ListItem, Paper, Typography, + Theme, } from "@mui/material"; import {ArrowBack, ExpandMore} from "@mui/icons-material"; import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; import {styled} from '@mui/material/styles'; +import {withTheme} from "../core/api"; import {StyledLink} from "../commons/StyledLink"; import {BackButton} from "../commons/BackButton"; @@ -33,19 +35,30 @@ const SectionTitle = styled(Typography)(({ theme }) => ({ })); const UnitCard = styled(Paper)(({ theme }) => ({ - background: 'rgba(255, 255, 255, 0.03)', - border: '1px solid rgba(255, 255, 255, 0.05)', + background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.03)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'}`, borderRadius: '10px', padding: theme.spacing(1.5, 2), transition: 'all 0.2s ease', '&:hover': { - background: 'rgba(233, 69, 96, 0.1)', - borderColor: 'rgba(233, 69, 96, 0.3)', + background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(233, 69, 96, 0.1)', + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(233, 69, 96, 0.3)', }, })); const BuildingCard = styled(UnitCard)(({ theme }) => ({})); +const UnitLink = styled(Link)(({ theme }) => ({ + color: theme.palette.text.primary, + textDecoration: 'none', + display: 'block', + '&:hover': { color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560' } +})); + +const LoadingText = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + interface RacePageState { mod: IMod, race: Irace, @@ -53,29 +66,29 @@ interface RacePageState { } -function Unit(unit: IUnitShort, modId: number, raceId: String) { +function Unit(unit: IUnitShort, modId: number, raceId: String, theme: Theme) { + const isDark = theme.palette.mode === 'dark'; + const borderColor = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'; + const bgColor = isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'; - return ( - - {unit.icon && } - {unit.name} - {unit.canDetect &&  } - - ) + return ( + + + {unit.icon && } + {unit.name} + {unit.canDetect &&  } + + + ) } function UnitSmall(unit: IUnitShort, modId: number, raceId: String) { @@ -94,13 +107,14 @@ function UnitSmall(unit: IUnitShort, modId: number, raceId: String) { src="/images/DETECT_YES.webp"/>}
) } -function Building(building: IBuildingShort, modId: number, raceId: String) { +function Building(building: IBuildingShort, modId: number, raceId: String, theme: Theme) { + const isDark = theme.palette.mode === 'dark'; return ( { +class Units extends React.Component { constructor(props: any) { @@ -157,7 +171,8 @@ class Units extends React.Component { } render() { - + const { theme } = this.props; + const isDark = theme.palette.mode === 'dark'; if (this.state && this.state.units) { @@ -165,65 +180,73 @@ class Units extends React.Component { document.title = this.state.buildings?.race.name } - const accordionSx = { + const accordionSx = isDark ? { background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', border: '1px solid rgba(255, 255, 255, 0.1)', borderRadius: '12px', '&:before': { display: 'none' }, '&.Mui-expanded': { margin: '0 0 8px 0' }, + } : { + background: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: '1px solid rgba(0, 0, 0, 0.1)', + borderRadius: '12px', + '&:before': { display: 'none' }, + '&.Mui-expanded': { margin: '0 0 8px 0' }, }; return (this.state.buildings != null ? - {this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId))} + {this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId, theme))} {this.state.buildings.buildingsAdvanced.length > 0 && - + Advanced buildings - {this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))} + {this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId, theme))}
} } + expandIcon={} aria-controls="units-accordion" id="units-accordion" - sx={{color: '#ffffff'}} + sx={{color: isDark ? '#dee2e6' : '#000000', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}} > - All units + All units - Infantry - {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))} + Infantry + {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId, theme))} - Tech + Tech - {this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))} + {this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId, theme))} - Support + Support - {this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))} + {this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId, theme))} -
: Loading +
: Loading ) } else { - return Loading...; + return Loading...; } } } +const UnitsWithTheme = withTheme(Units) as React.ComponentType; + class RacePageFast extends React.Component { constructor(props: any) { @@ -252,9 +275,11 @@ class RacePageFast extends React.Component { if (this.state != null && this.state.mod != null && this.state.race != null) { const backRef = "/mod/" + this.state.mod.id + const theme = this.props.theme; + const isDark = theme.palette.mode === 'dark'; return ( - + } @@ -267,26 +292,29 @@ class RacePageFast extends React.Component { {this.state.race.name} {this.state.mod.name} ({this.state.mod.version})
- +
); } else { return ( - + ); @@ -294,4 +322,4 @@ class RacePageFast extends React.Component { } } -export default withRouter(RacePageFast); \ No newline at end of file +export default withRouter(withTheme(RacePageFast)); diff --git a/src/pages/UnitPage.tsx b/src/pages/UnitPage.tsx index 0dbe11c..077b00d 100644 --- a/src/pages/UnitPage.tsx +++ b/src/pages/UnitPage.tsx @@ -18,7 +18,7 @@ import { TableCell, TableRow, Tooltip, - Typography, + Typography, Theme, } from "@mui/material"; import {ArrowBack, ExpandMore} from "@mui/icons-material"; import ArmorType from "../classes/ArmorType"; @@ -34,6 +34,7 @@ import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import Ability from "../classes/Ability"; import DeathExplosion from "../classes/DeathExplosion"; import {styled} from '@mui/material/styles'; +import {withTheme} from "../core/api"; import {DescriptionBox} from "../commons/DescriptionBox"; import {BackButton} from "../commons/BackButton"; @@ -44,19 +45,50 @@ const SectionTitle = styled(Typography)(({ theme }) => ({ })); const StatsPaper = styled(Paper)(({ theme }) => ({ - background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', - border: '1px solid rgba(255, 255, 255, 0.1)', + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, borderRadius: '12px', overflow: 'hidden', '& .MuiTableBody .MuiTableRow-root': { '&:last-child td, &:last-child th': { border: 0 }, }, '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { - color: '#e94560', + color: theme.palette.mode === 'dark' ? '#dee2e6' : '#e94560', fontWeight: 600, }, })); +const SergeantAccordion = styled(Accordion)(({ theme }) => ({ + background: theme.palette.mode === 'dark' + ? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)' + : 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)', + border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, + borderRadius: '12px', + mb: 1, + '&:before': { display: 'none' }, + '&.Mui-expanded': { margin: '0 0 8px 0' }, +})); + +const StyledDivider = styled(Divider)(({ theme }) => ({ + my: 3, + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', +})); + +const UnitTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 800, + mb: 1, + display: 'flex', + alignItems: 'center', + gap: 2, +})); + +const UnitSubtitle = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, + fontWeight: 500, +})); + interface UintPageState { unit: IUnit, @@ -64,10 +96,12 @@ interface UintPageState { } -function Unit(unit: IUnit, mod: IMod) { +function Unit(unit: IUnit, mod: IMod, theme: Theme) { document.title = unit.name + " — " + mod.name + const isDark = theme.palette.mode === 'dark'; + const morale = (unit.moraleMax !== null) ?  {unit.moraleMax} +{unit.moraleRegeneration}/s @@ -98,7 +132,7 @@ function Unit(unit: IUnit, mod: IMod) { const SergeantShort = (props: sergeantProps) => { return ( - + {props.icon && }   {props.name} {props.canDetect &&   - + {unit.icon && } {unit.name} - - + + {mod.name} ({mod.version}) - + @@ -297,22 +322,15 @@ function Unit(unit: IUnit, mod: IMod) { } {unit.sergeants.map(s => - + + sx={{color: isDark ? '#dee2e6' : '#000000', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' }}}> 0}/> - + - )} + )} {unit.moraleMax !== null && !(unit.moraleBrakeModifiers.find(m => m.reference.includes("accuracy_weapon_modifier") && m.value === 0.2) !== undefined && @@ -355,7 +373,7 @@ function Unit(unit: IUnit, mod: IMod) { Hotkey: {unit.hotkey} - + ) @@ -387,9 +405,10 @@ class UnitPage extends React.Component { if (this.state != null && this.state.unit != null && this.state.mod != null) { const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId + const { theme } = this.props; return ( - + } @@ -397,7 +416,7 @@ class UnitPage extends React.Component { > Back to race - {Unit(this.state.unit, this.state.mod)} + {Unit(this.state.unit, this.state.mod, theme)} ); } else { @@ -410,4 +429,4 @@ class UnitPage extends React.Component { } } -export default withRouter(UnitPage); \ No newline at end of file +export default withRouter(withTheme(UnitPage)); diff --git a/src/themes/theme.ts b/src/themes/theme.ts new file mode 100644 index 0000000..202411d --- /dev/null +++ b/src/themes/theme.ts @@ -0,0 +1,83 @@ +import { createTheme } from '@mui/material/styles'; + +export const darkTheme = createTheme({ + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 900, + lg: 1320, + xl: 1536, + }, + }, + palette: { + mode: 'dark', + primary: { + main: "#1a1a2e", + }, + background: { + default: '#0a0a0a', + paper: '#1a1a2e', + }, + text: { + primary: '#dee2e6', + secondary: '#dee2e6', + }, + }, + typography: { + fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif', + }, + shape: { + borderRadius: 12, + }, + components: { + MuiTableCell: { + styleOverrides: { + root: { + color: '#dee2e6', + }, + }, + }, + }, +}); + +export const lightTheme = createTheme({ + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 900, + lg: 1320, + xl: 1536, + }, + }, + palette: { + mode: 'light', + primary: { + main: "#1a1a2e", + }, + background: { + default: '#f5f5f5', + paper: '#ffffff', + }, + text: { + primary: 'rgba(0, 0, 0, 0.85)', + secondary: 'rgba(0, 0, 0, 0.6)', + }, + }, + typography: { + fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif', + }, + shape: { + borderRadius: 12, + }, + components: { + MuiTableCell: { + styleOverrides: { + root: { + color: 'rgba(0, 0, 0, 0.85)', + }, + }, + }, + }, +});