главный экран

This commit is contained in:
azykov@mail.ru
2026-09-04 17:22:49 +03:00
parent 03a7552b9a
commit 982bbce813
5 changed files with 129 additions and 2 deletions
+59
View File
@@ -36,6 +36,65 @@
font-size: 14px;
}
.exit-menu-btn {
padding: 8px 12px;
background: rgba(0, 0, 0, 0.25);
border: 3px solid var(--panel-border);
color: var(--text);
font-family: inherit;
font-size: 12px;
cursor: pointer;
}
.exit-menu-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.main-menu {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 40px;
background: var(--panel-bg);
}
.main-menu__title {
font-size: 32px;
margin: 0;
color: var(--accent);
text-shadow: 4px 4px 0 var(--panel-border);
}
.main-menu__buttons {
display: flex;
flex-direction: column;
gap: 16px;
width: 260px;
}
.menu-btn {
padding: 16px;
background: rgba(0, 0, 0, 0.25);
border: 4px solid var(--panel-border);
color: var(--text);
font-family: inherit;
font-size: 14px;
cursor: pointer;
}
.menu-btn:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.menu-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.main-row {
display: flex;
flex-direction: row;
+22 -1
View File
@@ -1,16 +1,37 @@
import { useState } from 'react';
import GameMap from './components/GameMap';
import MainMenu from './components/MainMenu';
import Sidebar from './components/Sidebar';
import Topbar from './components/Topbar';
import { GameStore } from './game/GameStore';
import { clearSave } from './game/persistence';
import './App.css';
type Screen = 'menu' | 'game';
function App() {
const [store] = useState(() => new GameStore());
// Always starts on the menu, regardless of whether a save exists — loading straight into a
// resumed game on every page (re)load isn't what was asked for.
const [screen, setScreen] = useState<Screen>('menu');
function handleNewGame() {
clearSave();
setScreen('game');
}
function handleExitToMenu() {
store.save();
setScreen('menu');
}
if (screen === 'menu') {
return <MainMenu onNewGame={handleNewGame} onContinue={() => setScreen('game')} />;
}
return (
<div className="app">
<Topbar store={store} />
<Topbar store={store} onExitToMenu={handleExitToMenu} />
<div className="main-row">
<Sidebar store={store} />
<GameMap store={store} />
+29
View File
@@ -0,0 +1,29 @@
import { useState } from 'react';
import { hasSave } from '../game/persistence';
interface Props {
onNewGame: () => void;
onContinue: () => void;
}
function MainMenu({ onNewGame, onContinue }: Props) {
// Computed once per mount — the menu remounts fresh every time the player returns to it (see
// App), so this naturally stays in sync with whether a save exists.
const [canContinue] = useState(hasSave);
return (
<div className="main-menu">
<h1 className="main-menu__title">Forest</h1>
<div className="main-menu__buttons">
<button type="button" className="menu-btn" onClick={onNewGame}>
Новая игра
</button>
<button type="button" className="menu-btn" disabled={!canContinue} onClick={onContinue}>
Продолжить
</button>
</div>
</div>
);
}
export default MainMenu;
+5 -1
View File
@@ -5,9 +5,10 @@ import PixelSprite from './PixelSprite';
interface Props {
store: GameStore;
onExitToMenu: () => void;
}
function Topbar({ store }: Props) {
function Topbar({ store, onExitToMenu }: Props) {
return (
<div className="topbar">
<h1 className="topbar__title">Forest</h1>
@@ -27,6 +28,9 @@ function Topbar({ store }: Props) {
<PixelSprite pixels={COIN_SPRITE} size={24} />
<span>{store.inventory.coins.toFixed(1)}</span>
</div>
<button type="button" className="exit-menu-btn" onClick={onExitToMenu}>
Выйти в меню
</button>
</div>
);
}
+14
View File
@@ -53,3 +53,17 @@ export function writeSave(data: SaveData): void {
// Ignore — e.g. private browsing mode or quota exceeded.
}
}
/** Deletes the saved game, if any — used when starting a brand new game. */
export function clearSave(): void {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {
// Ignore — e.g. private browsing mode.
}
}
/** Whether a valid, resumable save currently exists (used to enable/disable "Continue" in the menu). */
export function hasSave(): boolean {
return loadSave() !== null;
}